Agent and AI engineering · Practical technical guide · 2 November 2026
How x402 v2 turns HTTP 402 into a machine-readable quote an agent can pay with a signed stablecoin authorization, how the facilitator verifies and settles it, where AP2 mandates fit, and the client and merchant checks that stop replays and overpayment.
An agent that meets a price instead of a login
Jenya's research agent reads sample chapters before she orders books from Belgavi's Book Store. The store sells each sample-chapter download to agents for a small fee, with no accounts, stored cards or API keys. When the agent requests /chapters/7/sample, the store answers with status 402 Payment Required and a quote. The agent checks the quote against the limits Jenya gave it, signs a payment, and asks again. That is the x402 pattern.
This article walks through the mechanism as written in the x402 v2 specification (coinbase/x402 main at commit 0e6e8c5, read on 2 November 2026), shows how AP2 relates to it, and lists the checks that stop an autonomous buyer paying twice, paying the wrong party or overspending. Amounts are illustrative; nothing here is advice about holding or using any token.
Three headers carry the whole exchange
The HTTP transport keeps every protocol field in headers, each a base64-encoded JSON object. The response body is left to the server.
| Header | Direction | Carries |
|---|---|---|
| PAYMENT-REQUIRED | server to client, with status 402 | PaymentRequired: x402Version 2, the resource, and an accepts list of payment options |
| PAYMENT-SIGNATURE | client to server, on the retried request | PaymentPayload: the option the client accepted plus the scheme payload, for example a signature and authorization |
| PAYMENT-RESPONSE | server to client, with 200 or 402 | SettlementResponse: success, transaction hash, network, payer, and errorReason on failure |
Each entry in accepts names a scheme, a network in CAIP-2 form (the spec lists eip155:84532 as Base Sepolia and eip155:8453 as Base mainnet), an amount as a string in the token's atomic units, the token contract as asset, the recipient as payTo, maxTimeoutSeconds, and an extra object.
The exact scheme, the facilitator and the order of work
In the exact scheme the client pays precisely the quoted amount. On EVM networks the default asset transfer method is EIP-3009: the client signs a transferWithAuthorization message with from, to, value, validAfter, validBefore and a 32-byte random nonce, and the facilitator, not the client, submits it and pays gas. The scheme says the facilitator cannot modify the amount or the destination; it only broadcasts. Permit2 and ERC-7710 are the alternatives.
The merchant usually does not touch the chain. It sends the payload and its requirements to a facilitator. POST /verify is read-only: it checks the signature, the payer's balance, amount and validity window, token and network, and simulates the transfer, and it must not commit any state. POST /settle commits the payment, typically by broadcasting the transaction.
Version 2 also names the order of work. The default authorization flow is verify, run the resource, settle, respond, so funds move only after the resource completed. The upfront flow is settle, resource, respond, with no verify; escrow settles twice. A quote whose flow is not authorization must say so in extra.paymentFlow, clients must not build a payment for a flow they do not recognise, and when both are offered they should prefer authorization. One invariant covers all of them: at least one verify or settle must run before the resource executes.
Where AP2 fits today
AP2 records authority: signed mandates saying what the user allowed an agent to buy. x402 moves payment for one request. In AP2 main at commit e1ea56d (29 April 2026, read on 2 November 2026), the FAQ describes AP2 as payment-agnostic and points to the separate a2a-x402 repository as an implementation of A2A with x402 that will be aligned with AP2 over time. The human-present x402 sample README says an AP2-compatible x402 extension is coming soon.
The human-not-present x402 sample shows how the two can be joined. Its x402 payment processor, an MCP server, verifies the SD-JWT mandate chain, then checks that the keccak hash of that mandate chain equals the EIP-3009 authorization.nonce, recovers the signer with EIP-712 and compares it with from, and checks that to is the merchant wallet. The nonce is therefore not just random: it binds the payment to the mandate the user signed. Broadcasting on chain is off unless BROADCAST_ON_CHAIN=true. That sample runs over A2A and MCP rather than the HTTP PAYMENT-* headers, so AP2 does not itself specify HTTP 402; the two compose.
Worked example: check the quote before signing
The most important client code runs before any key is used. The snippet builds the 402 quote the store would send for one sample chapter, using the Base Sepolia USDC asset address from the spec's examples, encodes it as the header would arrive, and checks it against Jenya's policy: version 2, the exact scheme, an allowlisted network and asset, a pinned payTo, a maximum amount, a bounded maxTimeoutSeconds, and a recognised payment flow with authorization preferred. The second call rewrites payTo and raises the amount, as a tampering proxy or a mistaken price would.
Runnable with Python 3.11 or newer; the comments at the end show the real output of running it with Python 3.13. Amounts are illustrative atomic units.
import base64
import json
# What the store's 402 response would carry (amount is a string in the token's atomic units).
required = {
"x402Version": 2,
"error": "PAYMENT-SIGNATURE header is required",
"resource": {"url": "https://store.example/chapters/7/sample",
"description": "Sample chapter, The Monsoon Atlas", "mimeType": "application/pdf"},
"accepts": [{
"scheme": "exact", "network": "eip155:84532", "amount": "20000",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
"maxTimeoutSeconds": 60, "extra": {"name": "USDC", "version": "2"}}],
}
header = base64.b64encode(json.dumps(required).encode()).decode()
POLICY = {
"networks": {"eip155:84532"},
"assets": {"0x036CbD53842c5426634e7929541eC2318f3dCF7e"},
"pay_to": {"0x209693Bc6afc0C5328bA36FaF03C514EF312287C"},
"max_amount": 50000,
"max_timeout": 120,
}
KNOWN_FLOWS = {"authorization", "upfront"}
def problems(option):
found = []
if option["scheme"] != "exact":
found.append("scheme " + option["scheme"])
if option["network"] not in POLICY["networks"]:
found.append("network " + option["network"])
if option["asset"] not in POLICY["assets"]:
found.append("asset not allowlisted")
if option["payTo"] not in POLICY["pay_to"]:
found.append("payTo not pinned: " + option["payTo"])
if int(option["amount"]) > POLICY["max_amount"]:
found.append("amount %s above %d" % (option["amount"], POLICY["max_amount"]))
if not 0 < option["maxTimeoutSeconds"] <= POLICY["max_timeout"]:
found.append("maxTimeoutSeconds %d" % option["maxTimeoutSeconds"])
flow = option.get("extra", {}).get("paymentFlow", "authorization")
if flow not in KNOWN_FLOWS:
found.append("unknown paymentFlow " + flow)
return found
def choose(header_value):
body = json.loads(base64.b64decode(header_value))
if body.get("x402Version") != 2:
return None, ["x402Version %r" % body.get("x402Version")]
ok = [o for o in body["accepts"] if not problems(o)]
ok.sort(key=lambda o: o.get("extra", {}).get("paymentFlow", "authorization") != "authorization")
return (ok[0] if ok else None), [p for o in body["accepts"] for p in problems(o)]
option, notes = choose(header)
print("pay", option["amount"], "to", option["payTo"][:10], "notes", notes)
required["accepts"][0].update(payTo="0x9A7E000000000000000000000000000000000001", amount="900000")
option, notes = choose(base64.b64encode(json.dumps(required).encode()).decode())
print("pay", option, "notes", notes)
# pay 20000 to 0x209693Bc notes []
# pay None notes ['payTo not pinned: 0x9A7E000000000000000000000000000000000001', 'amount 900000 above 50000']An amount-only policy would have signed the second quote had the amount stayed the same. Pinning payTo to known merchant addresses, obtained from configuration rather than from the 402 response, is what stops a rewritten recipient.
Tradeoffs and failure modes
- Serving before settlement. Verify is read-only, so two concurrent copies of one payload can both verify. If the merchant responds before settle returns, one copy is served unpaid. Settle before responding, or keep a nonce cache.
- Skipping verify. A merchant that serves whenever a
PAYMENT-SIGNATUREheader is present breaks the spec invariant, and a payload signed for the wrong network is served and never paid. - Slow settlement. The authorization carries
validBefore. If the facilitator is slow and the broadcast lands after it, settlement fails withinvalid_exact_evm_payload_authorization_valid_before. The non-terminalsettlement_pendingcode carries the transaction hash so the merchant can reconcile before retrying. - Upfront and nothing to deliver. In the upfront flow the money moves first. If the file is then missing, the buyer was charged without delivery and needs a refund.
- Recipient and budget. A client that trusts whatever
payToandamountarrive has handed its spending policy to the network path.
How to verify and measure it
- Decode every
PAYMENT-REQUIREDheader you receive and log the full option list, not just the one you chose. - Record the nonce,
validBefore, verify and settle results and transaction hash per request; a response served without a settled transaction is an incident. - Send the same
PAYMENT-SIGNATUREtwice, concurrently and after settlement, against a test network, and confirm the second is refused. - Measure settle latency, including its slow tail, against
maxTimeoutSeconds. - Test the client with a rewritten
payTo, an inflated amount, an unknownpaymentFlowand the wrong network, and check that it declines each without signing.
The companion lab in numbers
The lab is a deterministic model: nothing is signed or called, prices are illustrative rupees (₹2.00 per chapter by default), and it assumes validBefore is request time plus maxTimeoutSeconds, a one-second verify, and settle latency with a slow tail up to eight times the base.
At the defaults, ten requests in the authorization flow give ten chapters served and ten payments settled (₹20.00), and both concurrent replays of the first payload are rejected at settlement. Switch to verify, respond, settle later: twelve chapters are served, two without a valid payment, and two replayed payloads are accepted. Turn on the merchant nonce cache, or have the replays arrive after settlement, and both are refused again. Raise the settle latency to 8 seconds and request 7 expires before settlement (8 times 8 plus 1 is 65 seconds against 60); raising maxTimeoutSeconds to 90 clears even a 10-second latency. Quote ₹6.00 against a ₹5.00 maximum and all ten are declined, or paid over budget with the check off. A proxy rewriting every third payTo is declined three times by a pinned client and paid three times when the pin is off. A missing chapter file costs nothing in the authorization flow, but the upfront flow records one charge without delivery.
Sources and scope
Checked against these sources on 2 November 2026. The Python policy check was run with Python 3.13 and its output is shown. The lab is a deterministic model; no facilitator, wallet or blockchain was called, and rupee amounts are illustrative.
- x402 specification v2 (coinbase/x402, commit 0e6e8c5)
- x402 v2 HTTP transport: PAYMENT-REQUIRED, PAYMENT-SIGNATURE, PAYMENT-RESPONSE (commit 0e6e8c5)
- x402 scheme exact on EVM: EIP-3009, Permit2, ERC-7710 (commit 0e6e8c5)
- AP2 FAQ: x402 and a2a-x402 (commit e1ea56d)
- AP2 sample: human-present purchases with x402 (commit e1ea56d)
- AP2 sample: x402 PSP MCP server, mandate-to-nonce binding (commit e1ea56d)
x402 v2 turns an HTTP 402 into a base64 PAYMENT-REQUIRED quote that an agent pays with a signed EIP-3009 authorization in PAYMENT-SIGNATURE, while a facilitator runs a read-only /verify and a state-committing /settle. Check amount, asset, network, payTo and paymentFlow before signing, prefer the authorization flow, never serve before a verify or settle, and size maxTimeoutSeconds for slow settlement. AP2 adds the user mandate; its x402 sample binds the mandate hash into the EIP-3009 nonce.