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:

StageMeaningRetry?
inputYour request body was malformedNo — fix the request
signatureThe signature did not recover to the claimed addressNo — fix the signing
feeThe service fee leg failedMaybe
verifyThe facilitator rejected the authorizationNo
settleVerified, but the broadcast failedYes
facilitatorThe facilitator itself was unreachable or erroredYes

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.

text
500 No facilitator registered for x402 version: undefined

Base64 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:

text
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#

Agent prompt
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#