DEV Community

Payload
Payload

Posted on

"The second most expensive x402 mistake: treating verify as settlement"

/verify returned isValid: true. So you did the work. Then /settle answered {"errorReason":"invalid_payload"} and a 400 that explains nothing. Or worse: settlement never completed at all, and the work is already delivered.

The second most expensive x402 mistake is treating a passing verify as proof of payment. Verify and settle answer different questions at different times, and the world changes between them.

Two gaps people confuse as one:

  1. The trust gap. Verify checks the authorization; settle moves the money. An EIP-3009 authorization valid at verify time can be worthless at settle time: the buyer withdrew funds between the calls, submitted the same authorization to two sellers at once, or reused a nonce. Circle's payments team raised exactly this double-spend pattern upstream (x402-foundation/x402#447). Verify is a point-in-time opinion, not a guarantee. Sellers who perform work on the strength of verify alone are doing unpaid work on credit.

  2. The rules gap. Settle has requirements the spec never mentions. In one documented case, verify passed on every attempt while settle rejected everything with invalid_payload. Root cause, found after days of debugging: the token name field had to be "USD Coin", not "USDC", and amounts had a $0.001 minimum. Neither requirement appears in the spec (x402-foundation/x402#961).

When verify rejects you instead, the failure is usually field-level and exact: the EIP-712 domain (name: "USD Coin", version: "2" for USDC, never guess, read the contract), struct type ordering (the Cantina audit found batch-settlement contracts building type strings in the wrong order), the validAfter/validBefore window (a slow agent signs at T and submits after expiry), nonce reuse, or a value that does not exactly equal the quoted amount in atomic units. Walk the checklist in order; the first failure you find is almost always the cause. And never "fix" nonce_already_used by minting a fresh authorization for the same intent without checking whether the first one settled. That converts a replay rejection into a double payment, which is article #1's mistake wearing a new hat.

The safe sequence:

  1. Sellers: settle before serving, or serve only what you would give away. Cap exposure per request and treat verify as advisory.
  2. Read the settle error literally. invalid_payload with a passing verify means a settle-time rule violation: exact token name string, amount minimums, network and asset match against the 402 requirements, EIP-3009 field formatting.
  3. Diff your payload against the 402 challenge field by field. The most common settle-only rejections are mismatches the verify step does not check.
  4. Do not loop verify to settle retries hoping the opaque error resolves itself. If settle rejects the payload, re-verifying the same payload changes nothing and burns time.
  5. Log both responses with timestamps. When the gap between verify and settle is large, suspect state change (funds moved) rather than payload bugs.

This is the failure class callx402 diagnose was built for. Hand it both responses and it runs the x402 doctor's failure classifiers over what you supplied, deterministically, and reports which stage failed and why:

callx402 diagnose --evidence '{"verifyResponse":{...},"settleResponse":{...}}'
Enter fullscreen mode Exit fullscreen mode

It also takes --target or @file. What it deliberately refuses to do: touch the facilitator. No re-verification, no resubmission, no retry. A doctor that can retry your payment is a doctor that can double-pay you, so diagnose is read-only by design. Two honest limits: classification is only as good as the evidence you hand it, and it does not do field-level EIP-3009 signature analysis. The checklist above is the narrowing tool for that; diagnose tells you which stage to aim it at.

Full writeups with sources in the callx402 troubleshooting docs: verify-ok-settle-fails and the EIP-3009 field checklist. When x402 breaks, callx402.

Top comments (0)