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:

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:

LogSays
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.

bash
# Anything that can read a receipt will do; this is the shape of the check.
cast receipt 0xabc123… --rpc-url https://mainnet.base.org

Layer 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_x402Means
trueAn AuthorizationUsed marker was found. On-chain proof.
nullNot examined. Usually a catalog-derived heuristic, and usually probably not x402
falseExamined and disproven — a plain transfer that happened to land on a payTo

Look up your own settlement:

bash
curl -sL "https://www.roundhouse.studio/api/v0/entities/<your-wallet>/settlements?limit=10" | jq

Note

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:

  1. Log the X-PAYMENT-RESPONSE for every paid request, with your own request id.
  2. Nightly, look up each txHash on-chain and confirm the Transfer amount and recipient match what you signed.
  3. Cross-check the total against your wallet's outbound in the index:
sql
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#