Services: Endpoints: Facilitators: Total Volume: 7d Vol: 24h Vol:
← Blog
ExplainerJune 21, 2026 · 5 min read

Anatomy of an x402 Payment Challenge

By spx

What 402 Payment Required actually means and what a x402 payment response looks like under the hood.

x402 turns HTTP’s long-unused 402 Payment Required into a way for machines to pay for APIs directly. A client hits an endpoint, gets a price, signs a payment, and retries the request, all inside normal HTTP.

But there’s a common misunderstanding: returning a 402 is not enough.

A 402 status code is just a signal. What makes it work is the structure of the response body. If that part is wrong or incomplete, no x402 client can actually pay you.


The basic flow

A working x402 exchange looks like this:

  1. Client requests a resource
  2. Server responds with 402 Payment Required + payment details
  3. Client signs a payment authorization locally
  4. Client retries the request with the payment proof (X-PAYMENT)
  5. Payment is verified and settled, and the server returns 200 OK

The key idea: the client never sends a blockchain transaction. It only signs a message. Settlement is handled separately.

👉 Read also: What is x402?


What a real 402 response looks like

The important part is the JSON body. A compliant x402 response includes an accepts array that describes how to pay:

{
  "x402Version": 2,
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0xAd04…a4a3",
      "maxAmountRequired": "2000",
      "resource": "https://api.example.com/v1/data",
      "description": "Premium data access",
      "mimeType": "application/json",
      "maxTimeoutSeconds": 300
    }
  ]
}

What each field is doing

  • accepts → the core of the protocol. If it’s missing, nothing works.
  • scheme → how payment is handled (usually exact)
  • network → CAIP-2 chain ID (not a human label)
  • asset → token contract (e.g. USDC)
  • payTo → recipient wallet
  • maxAmountRequired → amount in atomic units (not decimals)
  • resource → what this payment unlocks
  • maxTimeoutSeconds → how long this quote is valid

Everything here is meant to be machine-readable. No guessing, no parsing strings.


Body or header? Choose what you prefer

Everything above puts the challenge in the response body. That's how x402 v1 works, and it's still the most widely deployed.

x402 v2 moves the same information into HTTP headers: the server returns the requirements in a base64-encoded PAYMENT-REQUIRED header, the client replies with its signed payload in a PAYMENT-SIGNATURE header, and settlement is confirmed in a PAYMENT-RESPONSE header.

HTTP/1.1 402 Payment Required
WWW-Authenticate: x402
PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6Miwi...   # base64 JSON, same accepts[] structure

The encoded payload is the same accepts[] array — just base64-encoded so it survives header transmission cleanly.

Why bother? It keeps the payment layer orthogonal to the content. With the challenge in a header, the response body stays free for the actual resource and the request body stays free for real API parameters, so x402 drops into any existing API without hijacking the body to negotiate payment.

Backward compatibility. v2 doesn't break v1. Clients read both, preferring the v2 header and falling back to the body:

const payment = headers.get("PAYMENT-SIGNATURE") || headers.get("X-PAYMENT");

Note: By convention, you should use: WWW-Authenticate: x402 as it signals the payment scheme for a 402 response.

Paying it

The client reads one option from accepts[] and builds an EIP-3009 transferWithAuthorization payload:

{
  "from": "0xAgentWallet…",
  "to": "0xAd04…a4a3",
  "value": "2000",
  "validAfter": "0",
  "validBefore": "1714723200",
  "nonce": "0x…32-byte-random…"
}

It signs this locally with EIP-712 typed data, encodes the signed payload in base64, and replays the original request with it in the X-PAYMENT header. The server (or usually, a facilitator like Coinbase CDP) verifies the signature and amount, calls transferWithAuthorization on the token contract to settle on-chain, and returns 200 OK with the resource plus an X-PAYMENT-RESPONSE header carrying the settlement details.

The security falls out of the design: the signature commits to the exact amount, asset, and recipient, so a tampered quote invalidates it. The 32-byte nonce is recorded on-chain to avoid replay attacks.


The common mistake

A lot of implementations stop at the status code and return something like this:

{
  "error": "402 Payment Required",
  "price": "0.002 USDC",
  "network": "Base",
  "recipient": "0xAd04…a4a3"
}

It looks fine, but it’s not usable by x402 clients.

What’s missing:

  • No accepts[] → clients don’t know how to process it
  • No structured scheme or asset → nothing to sign against
  • No CAIP-2 network ID → ambiguous chain definition
  • No proper payment format → cannot generate a valid authorization

In practice, this means no SDK, no agent, and no facilitator can use it.

It’s just a human-readable price tag.


Quick sanity check

If you’re building this, your endpoint should satisfy:

  • x402Version + accepts[] present
  • Each option includes scheme, network, asset, payTo, maxAmountRequired
  • Network uses CAIP-2 format
  • Payment can be replayed via X-PAYMENT
  • Settlement actually happens on-chain

If any of these is missing, the endpoint isn't really usable even if it returns 402. Also, make sure you're actually using a facilitator unless you know what you're doing.


*At x402radar, we index the x402 ecosystem by actively paying endpoints to verify their behavior in real conditions. A 402 response alone is not enough; we only list services that are truly payable and work end-to-end.

The payment challenge must be correctly structured, and transactions must actually settle on-chain. When an endpoint shows up as unpayable, it’s almost always due to an invalid or incomplete challenge body.

Browse the ecosystem →*

Written by spx
Published June 21, 2026