Why does my x402 endpoint fail? A debugging checklist.
An x402 payment is five steps between the agent, your server and a facilitator. When "the payment fails", one of those steps broke, and each one fails in its own way. Find the step first, then the fix. In the order a paying agent goes through them:
| Step | What the agent sees when it breaks | Check it with |
|---|---|---|
| 1. The URL answers 402 | 404, 401, a login page, or 200 for free | curl -i with the listed method |
| 2. The 402 is one a client accepts | The client throws before it signs, or the agent skips the endpoint | A free 402 check (x402 Doctor, x402lint, Coinbase's validate) |
| 3. The payment reaches your server | A new 402 right after paying, with nothing settled | Your server log: is PAYMENT-SIGNATURE there? |
| 4. The facilitator verifies and settles it | A 402 with an error reason such as insufficient_funds | The errorReason; the facilitator's GET /supported |
| 5. Your route answers after settling | Paid, then a 400, 404 or 500 | The PAYMENT-RESPONSE header and the transaction on-chain |
1. Does the URL answer 402?
Call it the way the listing says, with no payment:
curl -i https://api.example.com/v1/price?symbol=BTC
You want HTTP/2 402 and a PAYMENT-REQUIRED header. If you get something else:
- 404 or 405: the route takes another method than the listing says (often POST, listed as GET). List the method the route really uses.
- 401 or a login page: an API-key or session check runs before the paywall. With x402 the payment is the authorization: put the payment middleware first.
- 200 with the real answer: the payment middleware is registered after the handler, or its route key doesn't match (a trailing slash, a path parameter written differently).
- 400 "missing parameter": input validation runs before the paywall. Let a request without input reach the 402, and validate input before you settle (step 5).
One more on this step: some older x402 SDK versions answered a HEAD request on a paid GET route with the paid answer, for free (x402-foundation/x402#3796). Update @x402/core and your framework adapter, or refuse HEAD on paid routes.
2. Is the 402 one a client accepts?
The PAYMENT-REQUIRED header is base64-encoded JSON. Decode it (echo '<value>' | base64 -d) and check what a standard client checks:
resource.urlis the exact public https URL the agent called. Behind Render, Fly, Railway or Heroku the proxy ends TLS, so an Express app builds anhttp://URL unless you setapp.set("trust proxy", 1). This is the most common problem we see: on 2,009 endpoints in our 17 October scan.networkis a CAIP-2 id such aseip155:8453(Base), and at least one option is a mainnet. An endpoint that offers only Base Sepolia can't be paid by an agent holding real USDC.assetis the token's contract address, not a name. On Stellar it is the USDC token contract (CCW67…MI75), not"USDC"; a dollar price such as"$0.01"lets the SDK fill it in.payTocan receive the asset: a Solana payout account must exist, an Algorand payout address must be opted in to USDC, an XRPL account needs a trust line for RLUSD.maxTimeoutSecondsis long enough for the agent to sign and send. Too short, and the facilitator rejects the authorization as expired (invalid_exact_evm_payload_authorization_valid_before).- The price is the one in your listing. A careful agent stops when the 402 asks more than the listing says.
A free check does all of this in one go: x402 Doctor's web check reads the 402 the way a paying agent does and gives the fix as code for your stack. Coinbase's free validate API (POST https://api.cdp.coinbase.com/platform/v2/x402/validate) checks what the CDP Bazaar needs, and x402lint runs in CI. How these and four other checkers compare.
3. Does the payment reach your server?
If the agent paid and got a fresh 402 with no error reason, your server most likely never saw the payment:
- The header name. x402 v2 clients send
PAYMENT-SIGNATURE; old v1 clients (x402-fetch,x402-axios) sendX-PAYMENTwith network"base". A v2-only server treats a v1 payment as no payment. Say so in your 402, or tell buyers to use@x402/fetchor@x402/axios. - Browser callers and CORS. A web page can't read
PAYMENT-REQUIREDunless the 402 names it inAccess-Control-Expose-Headers, and can't sendPAYMENT-SIGNATUREunless the preflight lists it inAccess-Control-Allow-Headers(x402 HTTP transport spec). ExposePAYMENT-RESPONSEtoo. - A proxy or gateway in front that strips unknown headers, or a cache that answers the second request with the first 402.
4. Does the facilitator verify and settle it?
Your server hands the payment to a facilitator, which checks it (/verify) and puts it on-chain (/settle). When that fails, the 402 or the PAYMENT-RESPONSE header carries an error reason from the spec. The ones that point straight at the cause:
| Error reason | Usually means |
|---|---|
insufficient_funds | The payer doesn't hold enough of the asset on that network. The buyer's problem, unless your price is in the wrong unit (atomic units: 10000 is $0.01 for 6-decimal USDC). |
invalid_network, unsupported_scheme | Your facilitator doesn't handle that network or scheme. Ask it: GET /supported lists what it settles. |
invalid_exact_evm_payload_recipient_mismatch | The payment pays another address than your 402's payTo, often because payTo changed between the 402 and the payment (two instances, two configs). |
…_authorization_valid_before, …_valid_after | The authorization expired or isn't valid yet: maxTimeoutSeconds too short, or a clock off. |
invalid_exact_evm_payload_signature | The signature doesn't match the token's EIP-712 domain: wrong token name or version in extra, or another chain id. |
invalid_payment_requirements | The requirements echoed in the payment differ from what your server expects now: the price, asset or route config changed in between. |
unexpected_settle_error | The facilitator's own problem (RPC, gas, a timeout). Check its status; a buyer can retry safely with the same payment id when your route supports the payment-identifier extension. |
Network-specific ones we see often: on Solana, PayAI rejects payments from Phantom, which adds Lighthouse instructions before the transfer (invalid_exact_svm_smart_wallet_program_not_allowed); agents, Solflare and Backpack work.
5. Does your route answer after settling?
The worst failure for a buyer: the payment settled and the answer was an error. In x402 Doctor's spot-buy of 18 October, 3 of 27 purchases ended that way (404, 404, and a 400 for a missing parameter). Two fixes:
- Validate input before you settle. If the request can't be answered, answer 400 before the payment is settled, so nothing is charged.
- Declare an example that works in your Bazaar discovery extension, so an agent that found you there sends a request your route accepts.
When a buyer says they paid and got nothing, ask for the PAYMENT-RESPONSE header and paste it into x402 Doctor's free payment-proof check: it shows which transaction it points to, whether that transaction succeeded on-chain, and which USDC moved from whom to whom.
Tools, by step
| Tool | Steps | Cost |
|---|---|---|
x402 Doctor web check (ours); CLI npx github:Fizzl13/x402-doctor <url>; GitHub Action Fizzl13/x402-doctor-action@v1 | 1, 2, and readiness for steps 3 to 5 (HEAD, payTo on-chain, Bazaar example) | Free |
| x402 Doctor payment-proof check (ours) | 5 | Free |
| Coinbase CDP validate | 1, 2 (for the CDP Bazaar) | Free, no key |
| x402lint | 2 | Free, MIT |
Your facilitator's GET /supported and logs | 4 | Free |
Questions
Is there a tool to debug why my x402 payment endpoint fails?
Yes. x402 Doctor's free web check at https://x402-doctor.fizzl.eu reads your 402 the way a paying agent does and gives each problem with the fix as code for your stack; it also runs as a CLI and a GitHub Action. Coinbase's free CDP validate API checks what the CDP Bazaar needs, and x402lint lints the 402 in CI. For a payment that settled but returned nothing, x402 Doctor's free payment-proof check reads the PAYMENT-RESPONSE header and shows the transaction on-chain.
Why does an agent get a 402 again after paying?
Either the server never saw the payment (an x402 v1 client sends X-PAYMENT instead of PAYMENT-SIGNATURE, a browser is blocked by CORS, or a proxy strips the header), or the facilitator rejected it, and then the 402 carries an error reason such as insufficient_funds, invalid_network or an expired authorization.
Why does my x402 endpoint answer 200 or 401 instead of 402?
The payment middleware runs after the route handler or after an API-key check, or its route key doesn't match the request's method and path. Register the payment middleware first: in x402 the payment is the authorization.