When "the x402 payment fails", one of five 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 | A free 402 check |
| 3. The payment reaches your server | A new 402 right after paying, nothing settled | Your 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 |
PAYMENT-RESPONSE 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 402 and a PAYMENT-REQUIRED header. If not:
- 404 or 405: the route takes another method than the listing says (often POST, listed as GET).
- 401 or a login page: an API-key check runs before the paywall. In 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.
- 400 "missing parameter": input validation runs before the paywall. Let a request without input reach the 402.
Older x402 SDK versions also answered a HEAD 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?
PAYMENT-REQUIRED is base64-encoded JSON. Decode it and check:
-
resource.urlis the exact public https URL. Behind Render, Fly, Railway or Heroku the proxy ends TLS, so Express builds anhttp://URL unless you setapp.set("trust proxy", 1). It's the most common problem we see: 2,009 endpoints in one daily scan. -
networkis a CAIP-2 id such aseip155:8453, and at least one option is a mainnet. -
assetis the token's contract address, not a name (on Stellar: the USDC token contract, not"USDC"). -
payTocan receive the asset (a Solana account that exists, an Algorand address opted in to USDC, an XRPL trust line). -
maxTimeoutSecondsgives the agent time to sign and send. - The price matches your listing. Careful agents stop when the 402 asks more.
3. Does the payment reach your server?
A fresh 402 with no error reason usually means your server never saw the payment:
-
Header name: x402 v2 clients send
PAYMENT-SIGNATURE; old v1 clients (x402-fetch,x402-axios) sendX-PAYMENT. A v2-only server treats a v1 payment as none. -
CORS: a browser can't read
PAYMENT-REQUIREDunless the 402 lists it inAccess-Control-Expose-Headers, and can't sendPAYMENT-SIGNATUREunless the preflight allows it inAccess-Control-Allow-Headers(x402 HTTP transport spec). - A proxy or cache that strips unknown headers or replays the first 402.
4. Does the facilitator verify and settle it?
The 402 or PAYMENT-RESPONSE then carries an error reason from the spec:
| Error reason | Usually means |
|---|---|
insufficient_funds |
The payer lacks the asset on that network (or your price is in the wrong unit: 10000 is $0.01 for 6-decimal USDC) |
invalid_network, unsupported_scheme
|
Your facilitator doesn't handle it; GET /supported lists what it settles |
invalid_exact_evm_payload_recipient_mismatch |
payTo changed between the 402 and the payment |
…_authorization_valid_before / …_valid_after
|
Expired or not yet valid: timeout too short, or a clock off |
invalid_exact_evm_payload_signature |
Wrong token name/version in extra, or another chain id |
invalid_payment_requirements |
The terms changed between the 402 and the payment |
unexpected_settle_error |
The facilitator's own problem (RPC, gas, timeout) |
5. Does your route answer after settling?
The worst case for a buyer: paid, then an error. In one of our weekly test buys, 3 of 27 purchases ended that way. Validate input before you settle, so a request you can't answer is never charged, and declare a Bazaar example that actually works.
When a buyer says they paid and got nothing, ask for the PAYMENT-RESPONSE header and look up the transaction it points to.
Free tools, by step
-
Steps 1–2 (and readiness for 3–5): x402 Doctor's web check reads your 402 the way a paying agent does and gives the fix as code; also a CLI (
npx github:Fizzl13/x402-doctor <url>) and a GitHub Action. Disclosure: we make it. -
Steps 1–2 for the CDP Bazaar: Coinbase's free validate API,
POST https://api.cdp.coinbase.com/platform/v2/x402/validate. - Step 2 in CI: x402lint.
-
Step 4: your facilitator's
GET /supportedand its logs. -
Step 5: x402 Doctor's payment-proof check reads a
PAYMENT-RESPONSEand shows the transaction on-chain.
The full guide, kept up to date: fizzl.eu/guides/x402-endpoint-fails.
Top comments (0)