On this page
The x402 V2 HTTP transport moves all protocol data into headers: the response body is left to the server. Each header value is a Base64-encoded JSON object. The object types themselves are defined in the V2 core specification. If you are new to the flow, start with What is x402?.
The three headers at a glance
| Header | Direction | Carries | Sent with |
|---|---|---|---|
PAYMENT-REQUIRED | Server to client | PaymentRequired | HTTP 402 when no valid payment is attached |
PAYMENT-SIGNATURE | Client to server | PaymentPayload | The retried request |
PAYMENT-RESPONSE | Server to client | SettlementResponse | The final response: 200 on success, 402 when settlement fails |
Encoding: Base64 JSON
To read a header, Base64-decode the value and parse the result as UTF-8 JSON. To write one, serialize the object and Base64-encode it. A few practical points:
- Header names are case-insensitive in HTTP (RFC 9110, section 5.1). Some servers emit
payment-requiredin lower case (the Settlens demo target does), so clients must match names case-insensitively. - The specification examples use standard Base64 with padding. Settlens' decoder accepts the standard and URL-safe alphabets and refuses values longer than 131,072 characters.
- Do not rely on the response body. The transport says bodies are a server implementation concern; a V2 client reads the terms from the header.
- Large values can hit proxy and server header-size limits, so keep descriptions and extensions short.
PAYMENT-REQUIRED
Sent with HTTP 402 when a request carries no valid payment. It tells the client what the resource is and every way it can be paid.
PaymentRequired fields
| Field | Type | Required | Meaning |
|---|---|---|---|
x402Version | number | Yes | Protocol version. Must be 2. |
error | string | No | Human-readable reason payment is required. |
resource | object | Yes | A ResourceInfo object describing the protected resource. |
accepts | array | Yes | The payment options the server accepts. Each entry is a PaymentRequirements object. |
extensions | object | No | Extension data keyed by extension identifier, each with info and schema. |
Payment requirements: each accepts entry
| Field | Type | Meaning |
|---|---|---|
scheme | string | Payment scheme, for example exact. |
network | string | CAIP-2 network, namespace:reference, for example eip155:84532. |
amount | string | Amount in the asset's atomic units, as an integer string. |
asset | string | Token contract address, or an ISO 4217 currency code for fiat rails. |
payTo | string | Recipient address, or a role constant such as "merchant". |
maxTimeoutSeconds | number | Maximum time allowed for payment completion. |
extra | object, optional | Scheme data. assetTransferMethod and paymentFlow are reserved protocol keys; for EIP-3009 tokens, name and version give the token's EIP-712 domain. |
ResourceInfo
url is required: it identifies the protected resource the payment is for. The optional fields are description, mimeType, serviceName (printable ASCII, at most 32 characters), tags (at most 5, each at most 32 characters) and iconUrl (absolute http or https URL, at most 2048 characters).
Decoded example
The response below is the example from the HTTP transport specification. The header value was produced at build time by encoding the decoded object underneath; it matches the specification byte for byte.
HTTP/1.1 402 Payment Required
Content-Type: application/json
PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MiwiZXJyb3IiOiJQQVlNRU5ULVNJR05BVFVSRSBoZWFkZXIgaXMgcmVxdWlyZWQiLCJyZXNvdXJjZSI6eyJ1cmwiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbS9wcmVtaXVtLWRhdGEiLCJkZXNjcmlwdGlvbiI6IkFjY2VzcyB0byBwcmVtaXVtIG1hcmtldCBkYXRhIiwibWltZVR5cGUiOiJhcHBsaWNhdGlvbi9qc29uIn0sImFjY2VwdHMiOlt7InNjaGVtZSI6ImV4YWN0IiwibmV0d29yayI6ImVpcDE1NTo4NDUzMiIsImFtb3VudCI6IjEwMDAwIiwiYXNzZXQiOiIweDAzNkNiRDUzODQyYzU0MjY2MzRlNzkyOTU0MWVDMjMxOGYzZENGN2UiLCJwYXlUbyI6IjB4MjA5NjkzQmM2YWZjMEM1MzI4YkEzNkZhRjAzQzUxNEVGMzEyMjg3QyIsIm1heFRpbWVvdXRTZWNvbmRzIjo2MCwiZXh0cmEiOnsibmFtZSI6IlVTREMiLCJ2ZXJzaW9uIjoiMiJ9fV19
{}
{
"x402Version": 2,
"error": "PAYMENT-SIGNATURE header is required",
"resource": {
"url": "https://api.example.com/premium-data",
"description": "Access to premium market data",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:84532",
"amount": "10000",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
"maxTimeoutSeconds": 60,
"extra": {
"name": "USDC",
"version": "2"
}
}
]
}
Read it as: pay 10000 atomic units of the token at 0x036C…CF7e on Base Sepolia (eip155:84532), whose EIP-712 domain name is USDC, to 0x2096…287C, within 60 seconds, for https://api.example.com/premium-data. With 6 decimals, as USDC has, that is 0.01.
Extensions
Servers advertise optional features in extensions, and clients echo them in their payload. A client must include at least the info it received: it may append information but cannot delete or overwrite it.
PAYMENT-SIGNATURE
Sent by the client on the retried request. It carries the payment authorization for exactly one of the advertised options.
PaymentPayload fields
| Field | Required | Meaning |
|---|---|---|
x402Version | Yes | Protocol version. |
resource | No | The ResourceInfo of the resource being accessed. |
accepted | Yes | The PaymentRequirements object the client chose from accepts. |
payload | Yes | Scheme-specific payment data. |
extensions | No | Extension data echoed from the server. |
For the exact scheme on EVM chains with the default EIP-3009 transfer method, payload holds a 65-byte EIP-712 signature and the authorization it signs: from, to, value, validAfter, validBefore and a 32-byte nonce. The EIP-3009 guide explains each field and how the signature is built. Other transfer methods, such as Permit2, use a different payload shape.
{
"x402Version": 2,
"resource": {
"url": "https://api.example.com/premium-data",
"description": "Access to premium market data",
"mimeType": "application/json"
},
"accepted": {
"scheme": "exact",
"network": "eip155:84532",
"amount": "10000",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
"maxTimeoutSeconds": 60,
"extra": {
"name": "USDC",
"version": "2"
}
},
"payload": {
"signature": "0x2d6a7588d6acca505cbf0d9a4a227e0c52c6c34008c8e8986a1283259764173608a2ce6496642e377d6da8dbbf5836e9bd15092f9ecab05ded3d6293af148b571c",
"authorization": {
"from": "0x857b06519E91e3A54538791bDbb0E22373e36b66",
"to": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
"value": "10000",
"validAfter": "1740672089",
"validBefore": "1740672154",
"nonce": "0xf3746613c2d920b5fdabc0856f2aeb2d4f88ee6037b8cc5d04a71a4462f13480"
}
}
}
Verify against your own terms
accepted is a copy written by the client. A resource server should check it against one of its own accepts entries and send its own requirement to the facilitator, so a client cannot lower the amount or change the recipient by editing the copy.
PAYMENT-RESPONSE
Sent by the server with the final response, after settlement. It carries a settlement response.
| Field | Required | Meaning |
|---|---|---|
success | Yes | Whether settlement succeeded. |
errorReason | No | Why settlement failed. Omitted on success. |
payer | No | The payer's address. |
transaction | Yes | Transaction hash, or an empty string when nothing was broadcast. Must be non-empty with settlement_pending. |
network | Yes | CAIP-2 network of the settlement. |
amount | No | Amount actually settled, in atomic units, when applicable. |
extensions | No | Extension data. |
{
"success": true,
"transaction": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"network": "eip155:84532",
"payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66"
}
{
"success": false,
"errorReason": "insufficient_funds",
"transaction": "",
"network": "eip155:84532",
"payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66"
}
settlement_pending is the one non-terminal error: the transaction was broadcast but its confirmation could not be established. The caller should reconcile on chain with the returned hash before deciding whether to retry. Every value of errorReason is listed in the x402 error codes reference.
Status codes
| Situation | HTTP status |
|---|---|
| Payment required | 402 |
| Invalid payment: malformed payload or requirements | 400 |
| Payment failed: verification or settlement failed | 402 |
| Server error during payment processing | 500 |
| Payment verified and settled | 200 |
Validation pitfalls
The official schema in @x402/core 2.28.0 checks types and required fields, and is permissive about values. The table below was computed when this page was built: each row changes one field of the example above (the V1 row replaces the whole object), then runs the pinned SDK schema and Settlens' offline checks against it, with https://api.example.com/premium-data as the requested URL.
| Pitfall | SDK schema | Settlens checks that fail or warn |
|---|---|---|
Decimal amountamount: "0.01" | Accepts | schema.payment_requirements fail |
Zero amountamount: "0" | Accepts | schema.payment_requirements fail |
V1 network namenetwork: "base-sepolia" | Rejects | schema.official_x402_sdk failschema.payment_requirements fail |
Number as a stringmaxTimeoutSeconds: "60" | Rejects | schema.official_x402_sdk failschema.payment_requirements fail |
Role constant as recipient on EVMpayTo: "merchant" | Accepts | schema.payment_requirements fail |
Relative resource URLresource.url: "/premium-data" | Accepts | schema.payment_required fail |
Resource URL of another endpointresource.url: ".../other-data" | Accepts | binding.resource_url fail |
Duplicate payment optionaccepts: [A, A] | Accepts | schema.duplicate_requirements warn |
One-day payment windowmaxTimeoutSeconds: 86400 | Accepts | freshness.timeout_window warn |
V1 object in a V2 headerx402Version: 1 | Accepts | schema.payment_required fail |
No EIP-712 domain hintsextra omitted | Accepts | None |
Three rows deserve a comment:
- Role constants. The core specification allows
payToto be a role constant such as "merchant". Oneip155networks Settlens requires a 20-byte address, because thetofield of an EIP-3009 authorization is an address. - V1 objects. The SDK schema accepts both protocol versions, so a V1 object passes it. A V2 header must carry
x402Version: 2, which Settlens checks separately. The V1 to V2 migration guide maps every renamed field. - Missing
extra. No scan check fails, yet the exact EVM specification makesextra.nameandextra.versionrequired for EIP-3009: a client cannot build the signature without the token's EIP-712 name and version, and Settlens' offlineverify-paymentrefuses to verify without them.
How Settlens checks each header
PAYMENT-REQUIRED
A passive scan sends the request without any payment header and runs these checks on the answer. The same content checks run offline when an agent passes the header to the MCP tool evaluate_payment_requirements.
| Check id | What it verifies |
|---|---|
transport.status_402 | A request without any payment header returns HTTP 402 Payment Required. |
transport.payment_required_header | The 402 response carries the PAYMENT-REQUIRED header defined by the x402 V2 HTTP transport. |
transport.payment_required_encoding | The PAYMENT-REQUIRED value is at most 131072 characters, uses only Base64 or Base64URL characters and decodes to valid JSON nested at most 64 levels deep. |
schema.official_x402_sdk | The decoded PaymentRequired object passes the parsePaymentRequired schema of the pinned official @x402/core 2.28.0 SDK. |
schema.payment_required | x402Version equals 2, resource is an object whose url is an absolute http(s) URL, and accepts is a non-empty array. |
binding.resource_url | resource.url equals the URL that was requested, including path and query (the fragment is ignored). In evaluate_payment_requirements this check is a warning when no resource URL is supplied, because the binding cannot be confirmed. |
schema.payment_requirements | Every accepts entry has a non-empty scheme, a CAIP-2 network, a positive integer atomic amount, a non-empty asset and payTo, and a positive maxTimeoutSeconds. On eip155 networks payTo must be a 20-byte hex address. |
schema.duplicate_requirements | No two accepts entries are identical. |
freshness.timeout_window | Every maxTimeoutSeconds value is at most 3600 seconds (one hour). |
stability.payment_requirements | Repeated identical unauthenticated requests (2 by default) return the same resource, accepts and extensions, ignoring the known ephemeral fields recentBlockhash and lastValidBlockHeight. Skipped when only one sample is available. |
PAYMENT-SIGNATURE
Passive scans never send this header, and Settlens refuses a user-supplied one. With the endpoint owner's authorization, the active probe sends one intentionally invalid value:
| Check id | What it verifies |
|---|---|
security.invalid_signature_fail_closed | With explicit authorization, Settlens sends one intentionally invalid PAYMENT-SIGNATURE (for example an all-zero EVM signature) and expects the resource not to answer with a 2xx status. |
transport.invalid_payment_status | The response to the intentionally invalid payment is HTTP 400 or 402. |
security.facilitator_rejects_invalid_signature | /verify returns isValid=false for the intentionally invalid, all-zero signature. Settlens never calls /settle. |
Separately, a captured authorization can be verified offline: the CLI's verify-payment reads a JSON file with the requirement and the payload (the same paymentRequirements and paymentPayload pair a server sends to /verify also works), and the MCP tool verify_payment_signature accepts the raw header value.
PAYMENT-RESPONSE
Settlens does not inspect PAYMENT-RESPONSE. It never completes a payment and never calls /settle, so an endpoint that behaves correctly never sends one to a scan.
Every check id, with its severity and remediation, is listed in the check reference. To run them against your own endpoint, see Assess or the CLI reference. To decode and check a single header in your browser, paste it into the x402 payment request checker.