{
  "slug": "troubleshooting-x402-payments",
  "title": "Troubleshooting x402 Payments",
  "description": "A field guide for debugging x402 integrations: malformed 402 responses, wrong asset decimals, stale quotes, repeated 402s after payment, CORS, facilitator failures, and manifest listing issues.",
  "category": "For Providers",
  "readMins": 11,
  "text": "Most x402 bugs are not mysterious blockchain bugs. They are usually one of a handful of mismatches: the server quoted one thing and verified another, the client paid on the wrong network, the manifest advertised stale data, or the payment proof expired before the retry. Start with the unpaid response Before testing wallets, facilitators, or settlement, confirm your endpoint returns a clean 402 with JSON an agent can parse: curl -i https://api.example.com/paid-resource You should see HTTP/1.1 402 Payment Required , content-type: application/json , x402Version , and at least one valid accepts[] entry. If that part is wrong, payment cannot work reliably no matter how good the client is. Common failure modes Symptom Likely cause Fix Client never sees a payable option accepts[] is missing, empty, or uses informal network names Return a real array with exact chain ids such as eip155:8453 . Payment succeeds on-chain, but retry still returns 402 Server verifies against a different payTo , asset, amount, or request reference than the quote Log the quoted requirements and the verification requirements side by side; they must match exactly. Amount looks wildly too high or too low Decimal confusion Use base units as strings. For 6-decimal USDC, 0.01 USDC is \"10000\" . Works locally, fails from browser clients CORS does not allow payment headers Allow the header your client uses, commonly PAYMENT-SIGNATURE or PAYMENT , and expose useful response headers. Works once, then replay attempts fail Correct behavior: proof is single-use Do not reuse a proof across requests. Pay again or use an explicit batching scheme if your protocol supports it. Quote worked a minute ago, then fails maxTimeoutSeconds expired Re-fetch the 402 immediately before paying, especially in agents that deliberate across multiple resources. Agent Bazaar listing does not update Manifest was changed but not re-submitted, or the URL is not publicly fetchable Fetch the manifest with plain curl , then re-submit with POST /submit . Debug the retry as a separate request Treat the paid retry as its own HTTP request with its own logs. For each failed retry, log: Request URL and method. The payment header name that was present, without logging the full secret/proof in public logs. The quoted network , asset , payTo , and amount . The verified network , asset , payTo , and amount extracted from the proof or facilitator response. Whether the quote/reference was expired or already used. That single comparison usually points straight at the mismatch. CORS checklist for browser-based clients Server-to-server agents rarely care about CORS, but browser clients do. If you expect web wallets or browser demos to call your endpoint, include the payment header in preflight handling: Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: content-type, payment, payment-signature Access-Control-Expose-Headers: x-payment-response Use the exact header names your client library sends. Header names are case-insensitive on the wire, but your framework's config may not feel that forgiving. Facilitator vs self-verification bugs If you use a facilitator, isolate whether the failure happens before or after facilitator verification: If the facilitator rejects the payment, inspect the client-side payment construction and wallet/network choice. If the facilitator accepts it but your server rejects it, inspect your local expected values and replay/expiry storage. If both accept it but the client still receives 402, inspect whether the retry is actually carrying the returned proof header. If you self-verify, add a confirmation-depth policy and be explicit about it. \"Transaction submitted\" is not the same as \"server is willing to serve the paid resource.\" Manifest listing issues If POST /submit fails, test the manifest from the outside world, not your local machine with special access: curl -i https://yourapi.com/.well-known/x402.json curl -X POST https://bazaar.saylorinnovations.com/submit \\ -H \"content-type: application/json\" \\ -d '{\"manifestUrl\":\"https://yourapi.com/.well-known/x402.json\"}' Typical listing blockers are private hosts, localhost URLs, invalid JSON, no paid resources, non-HTTPS URLs, and malformed accepts[] entries. The fix is almost always in the manifest, not in Agent Bazaar. The best sanity test Publish one tiny endpoint priced at a very small amount, test it end to end, then copy that exact payment path into more expensive resources. Once one route works, adding more x402 endpoints is mostly a manifest and routing exercise.",
  "related": [
    "how-to-build-an-x402-endpoint",
    "how-do-ai-agents-pay-for-apis",
    "x402-manifest-checklist"
  ]
}