x402 payment protocol

x402 payment protocol

x402 is the core of Otonix machine-to-machine payments.

It turns payment into a standard HTTP negotiation.

The idea

  1. Client requests a paid resource.

  2. Server replies 402 Payment Required with exact payment requirements.

  3. Client pays USDC on-chain.

  4. Client retries the same request with a payment proof.

Networks and assets

  • Network: Base mainnet (eip155:8453)

  • Asset: USDC on Base: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913

  • Treasury: 0x36363e573a3b5d4C22b6A64ef3080E5C101E685F

  • x402 version: 2

Full flow (domain registration example)

1

1) Request the resource (no payment proof)

POST /api/vercel/register

2

2) Receive 402 Payment Required

The server returns:

  • HTTP status: 402

  • Header: X-Payment-Required: <base64(requirements)>

  • Body: requirements in JSON

maxAmountRequired is in USDC 6 decimals.

  • 9.99 USDC9990000

3

3) Send USDC on-chain

Send a USDC transfer to the treasury on Base.

Wait for transaction confirmation.

You must keep:

  • txHash

  • payer address

  • orderId

4

4) Retry with payment proof

Send the same request body.

Attach X-Payment with base64-encoded JSON:

5

5) Server verifies then delivers

The server verifies all items below.

If valid, it returns 201 Created with a receipt.

Payment proof format

X-Payment is base64 of a JSON string.

Creating X-Payment (copy/paste)

You must send the raw base64 string as the header value.

Verification rules (server-side)

The server must verify all of the following:

  • orderId exists and is not expired (15-min TTL).

  • orderId is bound to this endpoint resource.

  • txHash has never been used before (replay protection).

  • On-chain receipt.from matches payer.

  • USDC Transfer amount is >= 99% of required amount.

  • Transaction status is success on Base.

circle-info

Order consumption should happen only after all checks pass. This keeps the flow idempotent.

On-chain verification (USDC Transfer)

Typical verifier logic:

  1. Fetch transaction receipt from Base RPC.

  2. Check receipt.status === "success".

  3. Check receipt.from === proof.payer.

  4. Find a Transfer log:

    • log.address === USDC contract

    • topics[0] === 0xddf252ad... (Transfer signature)

    • recipient topic matches treasury address

  5. Decode log.data as uint256.

  6. Convert to USDC: amount = value / 1e6.

  7. Compare with required amount.

x402 configuration endpoint

GET /api/x402/config

  • POST /api/vercel/register — domain registration (dynamic price)

  • POST /api/cherry/servers/:projectId — VPS provisioning (dynamic price)

Order security model (implementation notes)

  • Each 402 generates a unique orderId using crypto.randomBytes(16).toString("hex").

  • Orders expire after 15 minutes.

  • Orders should be auto-cleaned (example: every 60 seconds).

  • Orders are bound to the endpoint resource string (resource binding).

  • Each txHash must be single-use (replay protection).

Common 402 error variants

All of these return 402.

  • payment_required — first request, missing X-Payment.

  • payment_invalid — expired/invalid order id.

  • payment_invalid — resource mismatch.

  • payment_invalid — tx hash already used.

  • payment_invalid — on-chain tx failed.

  • payment_invalid — payer mismatch.

  • payment_invalid — insufficient amount.

TypeScript client integration

This snippet follows the expected request pattern.

Transaction recording

Every successful x402 payment should produce a transaction record.

Last updated