Pay with x402 (exact)
Your agent signs an EIP-3009
transferWithAuthorization over
EIP-712 typed data and sends the
signature. The API recovers the signer, then asks a facilitator — Coinbase
CDP — to verify it and broadcast the transfer. The signature is the payment:
there is no deposit address and no card.
Coinbase’s
x402 overview and
quickstart for buyers
cover the client side, including the @x402/fetch and @x402/axios wrappers
and the Go and Python clients that handle the 402 retry for you.
You need a token whose role grants Invoice:Pay — Member, Billing, or
Admin — an EOA whose key your agent controls, and that EOA funded with USDC
on the network the challenge names.
Create the experiment
payment.pay_url — the invoice
route you settle at.
Read the challenge
Call/pay with the method header and no credential:
PAYMENT-REQUIRED response header, base64-encoded,
not in the body. Decode it:
payTo is the merchant EOA, not a deposit address. asset is the raw ERC-20
contract address, and network its CAIP-2 chain id — read both from the
challenge rather than hardcoding them. extra carries the token’s EIP-712
domain, name and version.
Sign the authorization
Build the EIP-712 typed-data message for the token’stransferWithAuthorization:
from— your EOA.to—payTofrom the challenge.value— the challenge’samountverbatim. It is already in the token’s base units, so do not rescale it; the signed value must equal it exactly.nonce— a fresh 32-byte random value you generate. It is your own on-chain replay token; the challenge does not issue one.validAfter,validBefore— a window you choose.validAftermust be at or before now, andvalidBeforeno later thanmaxTimeoutSeconds(300 seconds) from now.
name and version
from the challenge’s extra, the chain id, and the challenge’s asset as the
verifying contract. The API recovers the signer over the same hash, so a domain
that differs in any field recovers a different address and the payment is
refused.
Retry with the signature
Base64-encode the signed payload as JSON and send it in thePAYMENT-SIGNATURE header:
200:
A
not_yet_settled verdict re-emits the same 402. That is not a rejection —
retry with the same signature. Re-signing burns the nonce for nothing.
Every other 402 after you send a signature is final: a recovered signer that
does not match, a wrong amount or recipient, an expired validBefore, or a
spent nonce. Resending the same signature will not clear any of them — fix the
cause and sign a corrected authorization, with a fresh nonce. The challenge
does not change, so reading it again returns the same terms.