Skip to main content
x402 paymentsThe 402 challenge flow
x402 payments

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

bash
curl -i "https://aly.store/api/x402/product/acme/prod_pattern?quantity=1&email=buyer@example.com"

Response 1 — 402 challenge

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

typescript
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

http
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
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 is created before settlement
The handler runs before the on-chain transfer settles. The x402 framework verifies that the signed authorization is valid (signature, nonce, expiry, sufficient balance), then attempts settlement after the response. If settlement fails, Aly voids the order and emits 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

StatusCause
402No payment provided; accepts in body.
400Bad payment payload (decoding, schema).
402 (re-issued)Payment signature invalid or expired.
404Product not eligible for x402 (physical, no file, missing wallet, over limit).
409Payment nonce already used.
429Per-IP checkout rate limit.
Updated

Was this page helpful?