01 · The handshake
Ask, get 402, pay, retry once.
First call without payment. The service answers 402 Payment Required with everything your agent needs: scheme, network, asset, amount, recipient, and the per-call cap.
1 · Ask without paying
curl -X POST https://verify402.onrender.com/verify \
-H "Content-Type: application/json" \
-H "x-idempotency-key: my-op-1" \
-d '{"pair":"USDC/WETH","dex":"uniswap-v3","sizeUsd":100000}'
# → HTTP 402 Payment Required
# accepts: eip155:8453 (USDC) or solana:… (USDC), payTo, amount, maxTimeoutSeconds
2 · Pay and retry with proof
# x402 clients (e.g. @x402/fetch) do this automatically:
# read the 402 → sign the exact payment → retry with X-PAYMENT
# The retry returns the verdict with an evidence_bundle.
Send x-idempotency-key on the first call
If the connection dies after settlement, the retry with the same key replays the same bytes for free — within ten minutes, three replays max. The key is the cheapest insurance on this rail.
02 · Receipts
Every paid response can be rechecked.
The response carries an evidence bundle so a third party can recompute the measurement without trusting the service: the hash of the canonical request, the hash of the actual response body, and the pinned block.
| Field | What it proves |
| requestHash | This exact request was the one answered — canonical bytes, not a paraphrase |
| responseHash | The exact bytes returned; a replay that doesn't match isn't free |
| pinnedBlock | The chain state the answer describes. Everything is a quote about one block. |
| verdict | edge, no_edge, or no_edge_costs_ate_it |
The recovery contract, in one paragraph
A settlement is staged, not recorded, when payment succeeds. It is promoted only by the official onAfterSettle hook, keyed on a server-generated operationId carried in the response and requiring a confirmed settle with a transaction reference — with the hash of the actual response bytes as an additional check. Equal output does not identify an operation: two requests that both return no_match are still distinct, and settling one never promotes the other. A failed or pending settlement promotes nothing; staged-unpromoted records age out silently after five minutes; the replay cap is per operation, shared by aliases. Reusing a key with changed request content, or from a different payer, is a 409 conflict resolved before the paywall — never the other operation's answer, never a charge. The content companion is a same-caller recovery alias, not a public cache. Two parts of this were broken in public by an independent auditor and fixed the same day: commit 4bf3c9c, then hardened after the x402 spec review (#3848).
03 · Before you sign
One free gate, one short-lived receipt.
POST /preflight is free. Give it an x402 payment request or a chain transaction; it returns PASS / CAUTION / BLOCK, typed reasons, the checks that actually ran, and the checks that did not. Every response carries an HMAC-signed receipt bound to the exact normalized intent and, where the chain is readable, a fresh pinned block. On EVM, a block older than 30s is CAUTION; older than 120s is BLOCK; a block with no timestamp is a 503.
Use it from an agent: verify402-mcp exposes the gate (no wallet) and the five paid checks (hot wallet, per-call x402) over MCP.
| Intent | What it needs | What it checks |
| payment | network, asset, amount, payTo, resource | scheme, CAIP-2 network, canonical USDC, amount/policy cap, payee allowlist, resource match, timeout, authorization window, payer balance on EVM, EVM block freshness |
| transaction | chainId (EVM 1/42161/8453), to, data, value, from | recipient, calldata decode, contract bytecode, approval/spender policy, swap deadline/minOut/recipient, eth_call simulation, EVM block freshness |
Payment request · abbreviated
curl -X POST https://verify402.onrender.com/preflight \
-H "Content-Type: application/json" \
-d '{
"kind": "payment",
"from": "0xYourWallet",
"payment": {
"scheme": "exact",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "10000",
"payTo": "0xAeEF086DD4e436c69d6d0F684b4c9F43392df9cb",
"resource": "https://verify402.onrender.com/verify"
},
"policy": { "maxAmountUsd": 0.25 }
}'
Response
{ "verdict": "PASS", "sign_allowed": true, "requires_ack": false,
"checked": ["scheme", "network", "asset", "amount", "payee", "payer_balance"],
"notChecked": ["recipient reputation", "sanctions screening", "counterparty identity / tool metadata", "prompt injection / hidden Unicode in tool output"],
"receipt": { "spec": "verify402/preflight-receipt@1", "intentHash": "…",
"blockNumber": 48277717, "signature": "…" } }
Verify before you trust the receipt
POST /preflight/verify with {"receipt":{…}} recomputes the HMAC and checks the expiry. No chain call. A CAUTION is an explicit decision, not a pass; a BLOCK is a refusal. If the chain cannot be read, /preflight returns 503 and issues no receipt, so the executor must fail closed. Reference executor: examples/preflight-guard.mjs — it refuses to call your signer without a verified receipt.