Understand x402
Payment errors
Find which stage a failed payment died at, and whether retrying can fix it.
Almost every broken x402 integration is one of the failures below. Start by finding which stage failed: about half never fix themselves on retry, so retrying blind burns gas and rate limit for nothing.
Read the stage first#
A well-behaved endpoint names the stage it failed at. Roundhouse's own endpoints use these:
| Stage | Meaning | Retry? |
|---|---|---|
input | Your request body was malformed | No — fix the request |
signature | The signature did not recover to the claimed address | No — fix the signing |
fee | The service fee leg failed | Maybe |
verify | The facilitator rejected the authorization | No |
settle | Verified, but the broadcast failed | Yes |
facilitator | The facilitator itself was unreachable or errored | Yes |
The line to internalise: verify failures never fix themselves. A bad signature, a reused
nonce, an expired window or an insufficient balance are all deterministic. If you retry a verify
failure unchanged, you will get the same answer forever.
The five that account for most of it#
1. Signing the wrong thing#
Symptom: verify fails with something like "signature does not authorize this transfer".
You used personal_sign over the JSON requirement instead of signing typed data over the token's
EIP-712 domain. No facilitator can verify the former — it is a signature over a different message
entirely. Every historical "402, no settlement" mystery has eventually been this.
Fix: signTypedData with primaryType: 'TransferWithAuthorization', domain taken from the token
contract. See anatomy of a payment.
2. Address casing#
Symptom: identical message, "signature does not authorize this transfer".
Typed-data libraries validate EIP-55 checksums inside the hash function, so a lowercase or mis-cased address produces a different hash — and the failure surfaces as a signature problem at the facilitator, which cannot be debugged from that end at all.
Fix: canonicalise every address to its checksummed form before anything hashes it.
3. Amount interpreted as decimal#
Symptom: "Payer balance N is less than the authorized M", with an absurd M.
maxAmountRequired is atomic units in a string. USDC has 6 decimals, so "10000" is $0.01. A
client that reads it as 10000 USDC will ask to move ten thousand dollars.
Fix: never convert. Pass the string through as value.
4. Chain not served#
Symptom: 500, or a verify failure saying something like "Chain 84532 not supported by this
facilitator". Reads like a payment problem; is a configuration problem.
x402.org/facilitator is testnet only. The Roundhouse facilitator serves Base mainnet.
Neither serves the other.
Fix: GET /supported on your facilitator before you build against it. See
schemes and networks.
5. Nonce reuse or a stale window#
Symptom: verify fails, and the same payload previously worked.
The nonce is single-use, enforced by the token contract. And an authorization whose validBefore
is within a few seconds is rejected rather than raced.
Fix: 32 fresh random bytes per attempt, and a validBefore comfortably beyond the resource's
maxTimeoutSeconds — ten minutes is a sane default.
The base64 trap#
Worth its own heading because the error message points nowhere useful.
500 No facilitator registered for x402 version: undefinedBase64 is the X-PAYMENT header's encoding. When you call /verify or /settle directly,
paymentPayload must be the decoded object — a facilitator reads x402Version, scheme and
network off it. Hand it a string and every one of those is undefined.
Roundhouse endpoint error codes#
For /v0/test/x402 and the SQL endpoint:
402 payment_malformed no EIP-3009 authorization + signature in the header
402 payment_invalid the facilitator rejected the payment (detail says why)
402 payment_failed verified, but settlement failed on-chain
409 key_already_issued that settlement already bought a key — pay again
500 key_issue_failed settled but key issuance broke; the tx hash is in the body
400 invalid_query failed the SQL sandbox checks
401 invalid_api_key key not found, revoked, or expired
402 insufficient_qu organization is out of Query Units
429 rate_limited too many requests this minute
400 query_failed the query ran but errored (timeout, bad column, …)409 key_already_issued is the one people misread: your payment succeeded, and a key was already
minted against that settlement. It is not a duplicate charge — it is a refusal to mint twice for one
payment.
A prompt for debugging one#
An x402 payment is failing. Diagnose it without retrying blindly.
1. Show me the raw 402 response body, decoded.
2. Show me the X-PAYMENT envelope you built, base64-decoded, and confirm:
- value is atomic units as a string
- to equals the requirement's payTo, EIP-55 checksummed
- validBefore is at least 300 seconds in the future
- nonce is 32 fresh random bytes
3. Fetch GET /supported on the facilitator and confirm the scheme and network
you are using appear in it.
4. Name the failure stage. If it is `verify`, do NOT retry — tell me which of
the four deterministic causes it is.Next steps#
- Anatomy of a payment — the fields, in order
- Facilitators — what verify actually checks
- Platform status — if the failure might be ours