DEV Community

Frits Zwager
Frits Zwager

Posted on Originally published at fizzl.eu

Why does my x402 endpoint fail? A debugging checklist

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.url is the exact public https URL. Behind Render, Fly, Railway or Heroku the proxy ends TLS, so Express builds an http:// URL unless you set app.set("trust proxy", 1). It's the most common problem we see: 2,009 endpoints in one daily scan.
  • network is a CAIP-2 id such as eip155:8453, and at least one option is a mainnet.
  • asset is the token's contract address, not a name (on Stellar: the USDC token contract, not "USDC").
  • payTo can receive the asset (a Solana account that exists, an Algorand address opted in to USDC, an XRPL trust line).
  • maxTimeoutSeconds gives 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) send X-PAYMENT. A v2-only server treats a v1 payment as none.
  • CORS: a browser can't read PAYMENT-REQUIRED unless the 402 lists it in Access-Control-Expose-Headers, and can't send PAYMENT-SIGNATURE unless the preflight allows it in Access-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 /supported and its logs.
  • Step 5: x402 Doctor's payment-proof check reads a PAYMENT-RESPONSE and shows the transaction on-chain.

The full guide, kept up to date: fizzl.eu/guides/x402-endpoint-fails.

Top comments (0)