Understand x402
Receipts and verification
Prove a payment happened, and tell a real settlement from a lookalike transfer.
Proving a payment happened takes three layers of evidence, from weakest to strongest. Keep them distinct: only the third is proof.
Layer 1: the receipt header#
X-PAYMENT-RESPONSE on the successful response, base64 JSON:
{ "success": true, "txHash": "0xabc123…", "networkId": "base", "payer": "0x…" }This is the facilitator telling you what it did. Keep every one — it is your reconciliation key, and
without the txHash you cannot connect a request in your logs to a movement on-chain.
It is evidence, not proof: it is an assertion by a third party. Which is fine, because it names the one thing that is checkable.
Layer 2: the transaction#
Look up the hash. A genuine exact settlement contains two logs from the token contract:
| Log | Says |
|---|---|
Transfer(from, to, value) | The money moved |
AuthorizationUsed(authorizer, nonce) | It moved via a signed EIP-3009 authorization |
The second one is what matters. A plain transfer() call produces only the first, so a transfer to
a known service wallet looks like a payment but proves nothing about consent or purpose.
# Anything that can read a receipt will do; this is the shape of the check.
cast receipt 0xabc123… --rpc-url https://mainnet.base.orgLayer 3: the index#
Roundhouse runs exactly this check on every candidate settlement and records the result as a
three-valued confidence, which is why /stats can lead with a verified population
rather than a blended total:
verified_x402 | Means |
|---|---|
true | An AuthorizationUsed marker was found. On-chain proof. |
null | Not examined. Usually a catalog-derived heuristic, and usually probably not x402 |
false | Examined and disproven — a plain transfer that happened to land on a payTo |
Look up your own settlement:
curl -sL "https://www.roundhouse.studio/api/v0/entities/<your-wallet>/settlements?limit=10" | jqNote
Do not blend the three values and call the sum "verified volume". Per-entity
rollups (mv_entity_rollups) currently do sum all three, which is stated
plainly on the pages that use them. When a number needs to mean proven, filter
on verified_x402 = true yourself.
Two things that distort a naive reading#
Fee proxies. Some routes send buyer → proxy → merchant inside one transaction: two Transfer
logs, one payment. An indexer that records the first leg has the proxy as the payee and the gross
amount as the price, and the merchant never appears. Roundhouse collapses the chain so payee is
the merchant, records the fee separately, and audits the correction. If a figure seems to belong to
an intermediary rather than a business, this is usually why.
Unpriced tokens. amount_usd is NULL when a token has no USD rate at that time — the index
refuses to guess a price. So a count and a volume are computed over different populations, and
every volume figure is a floor. /stats states the unpriced count for exactly this reason.
Reconciling your own spend#
The honest procedure, in order:
- Log the
X-PAYMENT-RESPONSEfor every paid request, with your own request id. - Nightly, look up each
txHashon-chain and confirm theTransferamount and recipient match what you signed. - Cross-check the total against your wallet's outbound in the index:
select count(*) as settlements,
sum(amount_usd) as usd,
count(*) filter (where verified_x402) as proven
from settlements
where payer = lower('0xYourWallet')
and block_time > now() - interval '30 days'A mismatch in either direction is informative. Fewer rows in the index than in your logs means a
gap (check /status); more means something else is paying from your address.
Next steps#
- Errors — when a payment does not settle
- Coverage and confidence — how much of the chain we see
- The data model — what a settlement row holds