The 402 challenge flow
Understand the two-step show-price-then-prove-payment flow behind agent-native digital purchases.
Exact request/response shapes for the discovery 402, the replay with Payment header, and the resulting order.
The 402 challenge flow is the heart of x402. A client makes a normal GET; if the resource is gated, the server replies with 402 Payment Required and a structured body describing how to pay. The client pays, replays with proof, and gets the resource.
Request 1 — discovery
curl -i "https://aly.store/api/x402/product/acme/prod_pattern?quantity=1&email=buyer@example.com"Response 1 — 402 challenge
HTTP/1.1 402 Payment RequiredContent-Type: application/jsonX402-Version: 1 { "x402Version": 1, "accepts": [ { "scheme": "exact", "network": "base", "asset": "USDC", "assetAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "maxAmountRequired": "5000000", "payTo": "0xabc...123", "resource": "/api/x402/product/acme/prod_pattern?quantity=1&email=buyer@example.com", "description": "Knitting Pattern PDF", "mimeType": "application/pdf", "outputSchema": { "$ref": "https://aly.store/x402/schemas/order.json" }, "maxTimeoutSeconds": 600, "extra": { "nonce": "0x9af2...", "validBefore": 1748112600 } }, { "scheme": "exact", "network": "polygon", "asset": "USDC", "...": "..." } ], "error": "X-PAYMENT_required"}The client picks one of the accepts entries — usually the operator's preferred chain if the wallet supports it.
Constructing the payment
Use an x402-aware client (e.g. @x402/evm). The client builds an EIP-712 TransferAuthorizationfor the chosen asset, signs it with the buyer's key, and base64-encodes the bundle into the Payment header.
import { payX402 } from "@x402/evm"; const challenge = await fetch(url).then((r) => r.json());const accept = challenge.accepts.find((a) => a.network === "base"); const payment = await payX402({ accept, wallet, // a signer for the chosen chain}); const res = await fetch(url, { headers: { Payment: payment.header, "Payment-Signature": payment.signature },});Request 2 — replay with proof
GET /api/x402/product/acme/prod_pattern?quantity=1&email=buyer@example.com HTTP/1.1Host: aly.storePayment: eyJ4NDAyVmVyc2lvbiI6MSwic2NoZW1lIjoiZXhhY3QiLCJuZXR3b3JrIjoiYmFzZSIsInBheWxvYWQiOnsiZ...Payment-Signature: 0x4f1c...Response 2 — 200 with the goods
HTTP/1.1 200 OKContent-Type: application/jsonPAYMENT-RESPONSE: {"txHash":"0xfa3c...","network":"base","blockNumber":12345678} { "order": { "id": "ord_72...4b", "site_slug": "acme", "product_id": "prod_pattern", "quantity": 1, "total": { "amount": 5000000, "currency": "USDC" }, "payment": { "network": "base", "txHash": "0xfa3c..." }, "download_url": "https://cdn.aly.store/orders/ord_72...4b/pattern.pdf", "expires_at": "2026-05-19T16:35:00.000Z" }}order.refunded.Idempotency
The order id is derived from a hash of the payment payload. Re-trying the same request returns the same order (and skips re-creating it). This protects against replay and accidental double-charges from retried HTTP.
Errors
| Status | Cause |
|---|---|
| 402 | No payment provided; accepts in body. |
| 400 | Bad payment payload (decoding, schema). |
| 402 (re-issued) | Payment signature invalid or expired. |
| 404 | Product not eligible for x402 (physical, no file, missing wallet, over limit). |
| 409 | Payment nonce already used. |
| 429 | Per-IP checkout rate limit. |
Was this page helpful?