How do I verify a USDC payment I just made to another seller?
After you pay an x402 seller, read the PAYMENT-RESPONSE (or X-PAYMENT-RESPONSE) header, decode it to get the Base transaction hash, and run that hash through Kertivo Check. Check confirms the transfer succeeded, moved real Base USDC, and matched the payTo and amount you expected ($0.01 per call). Free practice: send the body you plan to send Check to POST https://kertivo.com/demo. It reads your fields exactly like paid Check (aliases, match, warnings, the 400 guards) but always checks one fixed real transaction, so you can test your mapping before paying.
Who this is for
- Agents that just paid a third-party x402 endpoint and need an independent on-chain receipt before they trust the body.
- Buyers debugging "I got a 200 but I am not sure money moved" or "I got an error but I might still have been charged."
- Integrators wiring
@x402/fetch(or any x402 client) who want a copy-paste verify step after every paid call.
Why the seller's HTTP status is not enough
Paying another seller creates two outcomes: settlement (USDC on Base) and delivery (their HTTP response). They can disagree. A seller can return 200 without a settle header, return an error after money moved, or leave you with only an EIP-3009 authorization that was never used.
The settle header is the bridge. When present, decodePaymentResponseHeader (from @x402/fetch) yields { success, transaction, network, payer }. The transaction field is the Base tx hash you feed to Check. If the header is missing, either the call was free, the seller did not settle, or you need the authorization path ({from, nonce, validBefore}) instead of a hash.
How it works (evidence)
- Call the seller with an x402-paying client.
- On the response, read
PAYMENT-RESPONSEorX-PAYMENT-RESPONSE. - Decode it. Require
success === trueand a non-emptytransaction. - Call
POST https://kertivo.com/checkwith{ txHash: settle.transaction, expectedPayTo, expectedAmount }. Aliases work too:tx_hashorhashfor the hash,toorrecipientfor the payee, andamountfor an exact amount. Usemin_amount(ormin) instead when you only need a floor, andpayer(orfrom) to require that a specific wallet paid. - Read
settlement.verdict(settled,not_settled, orunconfirmedfor a spent authorization whose use can't be confirmed, which you should never treat as paid) and itsreason, thenmatch.okplus the individualpayToMatch,amountMatch,minAmountMatch, andpayerMatchfields, thewarnings, and the signedattestation.
Amounts are decimal USDC, not atomic units. A seller's 402 quotes amount in atomic units (6 decimals), so 10000 there means "0.01" here. Passing 10000 would ask Check whether you paid 10,000 USDC, and amountMatch would come back false. When the number you passed equals the transfer's atomic amount, Check also adds the warning amount_looks_atomic so the mistake is easy to spot.
Copy-paste for that flow is already on /examples as verifyLastPayment(sellerResponse, expectedPayTo, expectedAmount). It decodes the header, then calls Check through a paying @x402/fetch wrapper. Pass expectedAmount as decimal USDC (for example "0.01").
What Check confirms on-chain (Base mainnet, native USDC 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 only):
| Check | Why it matters |
|---|---|
| Tx succeeded | A revert moved nothing. |
| Real Base USDC transferred | Lookalike "USDC" contracts and bridged USDbC are not what Base x402 sellers quote. |
| Recipient and amount match | Compare to the seller's 402 payTo and amount. |
| Not a zero-value transfer | 0 USDC is the usual address-poisoning fingerprint, not a payment. |
Funding (external | self_funded | unknown) is extra context about where the payer got the USDC. Regular Check uses a recent block window. /check/deep ($0.05) reads the payer's history from an index instead: the latest 500 transfers before the payment plus up to five more pages, with coverage.historyComplete telling you whether that was the whole history.
Try it
- Free shape check (no wallet):
curl -s -X POST https://kertivo.com/demo. To rehearse your own field names, send them:curl -s -X POST https://kertivo.com/demo -H 'content-type: application/json' -d '{"hash":"0x…","to":"0x509E55c4F898B43C856Fb0E51048B1b7bFBC9eDE","amount":"1"}'. The response showsinputAliases, anyignoredFields,match,warnings, and arehearsalnote; your hash is swapped for the fixture tx, somatchcompares your expectations against that fixture payment. - See the Check price handshake without paying:
curl -si -X POST https://kertivo.com/check -H 'content-type: application/json' -d '{"txHash":"0x…"}'— expect HTTP 402, amount10000atomic ($0.01), networkeip155:8453, payTo0x509E55c4F898B43C856Fb0E51048B1b7bFBC9eDE. - After a real seller call: decode
PAYMENT-RESPONSE, then retry Check with the payment header (any x402 client). Full JS is on /examples.
You are only charged when Check answers with HTTP 200. The first, unpaid call is always a 402; Check reads your body on the paid retry, and a wrong chain or a token other than USDC then returns a 400 (unsupported_chain or unsupported_token) before anything settles, so it costs nothing. POST /demo returns the same 400s for free.
FAQ
The seller returned 200 but there is no PAYMENT-RESPONSE. Was I charged?
Maybe not. No settle header often means free tier, facilitator-side settle without that header, or a failed settlement. If you still have the EIP-3009 authorization, send it instead of a hash: {"authorization": {"from": "0x…", "nonce": "0x…", "validBefore": 1760000000}} (the flat {from, nonce, validBefore} body works too). The answer is authorization_used, authorization_canceled, authorization_unused_pending, or authorization_unused_or_expired (or authorization_spent_unconfirmed if the nonce is spent but no matching event turns up, for example when validBefore is missing; send validBefore to narrow the search). If you have a candidate tx hash from your wallet or a facilitator explorer, Check that hash with the expected payTo.
decodePaymentResponseHeader says success false.
Treat it as the seller reporting settlement failure. Do not assume money moved. If your wallet shows a USDC out, Check that hash independently.
Can I verify someone else's payment, not mine?
Yes. Check takes any Base USDC tx hash. Pass the seller's payTo as expectedPayTo so match is meaningful.
I paid and got nothing useful back. What next?
If Check says settled, keep the hash and file a free public claim at POST https://kertivo.com/claim (browse Claims). Settlement proof and delivery are separate.
Does verifying with Kertivo tell the seller I checked them?
Check only reads Base and returns evidence to you. It does not call the seller again.
Limits
- Base mainnet and native USDC only.
- Check proves on-chain settlement, not that the seller's data is correct or that they will deliver next time.
- Missing settle headers are common on some async or facilitator paths; you may need a wallet tx hash or the authorization nonce path.
- Regular Check funding coverage can be
unknown; deep scan costs more and reads indexed payer history (latest 500 transfers plus up to five pages), and a very busy wallet comes back withcoverage.historyComplete: false. - Kertivo holds no funds and does not arbitrate disputes. It reports evidence.
