Your agent found a paid endpoint, decoded the 402, and signed the payment. The answer is still 402. When x402 payments fail at the signing step, it is almost always one of two things: the authorization expired before it settled, or its nonce was already spent. Both are easy to create by accident and easy to fix once you can see them.
This post uses a fictional endpoint and a fictional wallet. The mechanics are the real EIP-3009 ones every USDC-on-Base x402 payment shares.
What you actually sign
An x402 payment on an EVM chain is not a transaction. It is a signed EIP-3009 transferWithAuthorization message with six fields:
-
from- your agent's wallet -
to- thepayTofrom the payment requirements -
value- the exactamountfrom the requirements, as a decimal string -
validAfter- the earliest timestamp the authorization may be used -
validBefore- the latest -
nonce- a one-timebytes32identifier
The server (or its facilitator) broadcasts it and pays the gas. Your signature is the whole job, and two of these fields cause nearly all first-attempt failures.
Failure 1: the window closed before settlement
validBefore is checked on-chain at settlement time, not when you sign. Anything that delays settlement eats your window: the agent paused between reading the requirements and signing, the facilitator is busy, the block is slow.
The classic version: an agent decodes the requirements, does a few seconds of "thinking" (or an LLM round-trip), then signs with validBefore copied from the requirements' own deadline. By the time the transaction lands, the deadline has passed, and the settlement reverts.
Two rules prevent it:
const now = Math.floor(Date.now() / 1000);
const validAfter = 0; // valid immediately
const validBefore = now + requirements.maxTimeoutSeconds - 60; // keep a buffer
- Compute
validBeforefrom your own clock at signing time, capped bymaxTimeoutSecondsfrom the requirements, minus a buffer for settlement latency. Sixty seconds is a sane minimum. - Sign last. Read the requirements, do every other thing your agent needs to do, and sign only when you are ready to fire the paid request. A signature with a ticking clock is the final step, never an intermediate result.
The mirror-image failure exists too: a clock running ahead sets validAfter in the future, and the authorization is not yet valid. If your agent runs somewhere with questionable time, use validAfter = 0 and fix the clock.
Failure 2: the nonce was already spent
EIP-3009 nonces are single-use. The USDC contract tracks each one in authorizationState[from][nonce]; once consumed, that authorization can never settle again. Two ways to burn a nonce accidentally:
Sequential nonces. If your wallet software numbers authorizations 1, 2, 3, two agents sharing the wallet will eventually pick the same number. EIP-3009 expects a random 32-byte nonce, which makes collisions a non-event:
const nonce = "0x" + Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex");
Retrying with the same signed payload. The agent sends the paid request, the connection times out, and it retries with the identical payload. But the first attempt may have settled, or be on its way to settling. The retry then fails with an already-used authorization, and the agent concludes the payment "didn't work" when it may actually have paid.
The safe retry is a lookup, not a re-send:
- On timeout, read the order or resource status first (most x402 APIs, and every well-behaved one, give you a way to ask).
- If you must pay again, sign a fresh payload with a fresh nonce. Never re-sign the same nonce, and never reuse a payload across orders.
A 60-second pre-send checklist
Before the request leaves the agent, decode the payload you are about to send and check it against the requirements:
-
toequalspayTo, character for character, checksum included -
valueequalsamount, decimal string, no JSON number, no float -
validBeforeis at least a minute in the future and insidemaxTimeoutSeconds - the nonce has never been used by this wallet (random, and you have not sent it before)
- the EIP-712 domain matches the chain and asset in the requirements (
name,version, chain id, verifying contract)
Five checks. Every one of them fails loudly on-chain if you skip it, and none of them costs anything off-chain.
When it still fails
A repeated 402 after these checks usually carries a reason in the response body. Read it before re-signing; "invalid" with a reason is the server telling you which of the six fields it disagrees with. Re-signing the same wrong thing with a fresh nonce just burns another attempt.
The payment requirements, the header names, and the reference clients are in the protocol documentation: https://docs.x402.org
This article was generated by an AI system.
Top comments (0)