Skip to main content

X402: HTTP-native stablecoin payments

X402 is an open payment protocol from Coinbase that reactivates the HTTP 402 Payment Required status code for machine-to-machine stablecoin payments. It is complementary to L402 — same goal of programmable, accountless payments inside HTTP — just settling in USDC on Base instead of sats on the Lightning Network.

Key properties

  • Stablecoin-denominated. Payments settle in USDC on Base (EVM L2), so the amount a client sees in the 402 response is the exact number of dollars they will spend. No FX.
  • On-chain and low-friction. Base mainnet settlement is sub-second and costs a fraction of a cent in gas. No counterparty risk after the facilitator returns success.
  • Header-driven. The client attaches one base64 X-PAYMENT header to the original request and re-submits. The server verifies + settles via a facilitator and returns 200 with an X-Payment-Response header carrying the settlement proof.
  • Agent-friendly. The flow is the same for humans and autonomous agents, and it’s supported out of the box by the Coinbase SDK as well as the open x402-axios, x402-fetch, and Python x402[httpx] clients. Learn more at x402.org.

markdown2pdf.ai and X402

markdown2pdf.ai accepts both L402 (Lightning) and X402 (USDC on Base). Pick whichever rail your agent already has a wallet for.
  • L402: 5 sats per PDF, instant via Lightning.
  • X402: $0.01 USDC per PDF, instant via Base.
You pay per document. Re-submitting the same markdown reuses the paid receipt — our server dedupes on a sha256 of the markdown body for both rails.

Flow

1

POST /markdown with your markdown content.

If you haven’t paid yet, the server returns 402 with a body that contains an offers[] array. When X402 is enabled there are two entries — one for Lightning and one for X402.
2

Read the X402 offer.

The X402 offer carries payment_methods: ["x402"] and a metadata.x402 block with the native x402 accepts[] array — the payment requirements the facilitator will validate against (network, asset, payTo, maxAmountRequired, scheme).
3

Sign an X-PAYMENT payload.

Use any x402-aware client (Coinbase SDK, x402-axios, x402-fetch, or Python x402[httpx]) to construct and base64-encode a PaymentPayload authorising the transfer.
4

Re-submit POST /markdown with X-PAYMENT.

Send the exact same request body, plus X-PAYMENT: <base64> as a header.
5

Server verifies + settles via the facilitator, returns 200.

A successful response carries X-Payment-Response: <base64> — the base64-encoded SettleResponse with the on-chain transaction hash. Your agent can log this for auditing, or ignore it and proceed straight to polling the job status.
Alternative shortcut. If your client already speaks x402 natively and does not want to parse the L402 envelope, it can ignore the outer body entirely and read accepts[] straight from offers[].metadata.x402.accepts — no second round-trip required. We also expose a dedicated POST /payment_request/x402 endpoint for clients that prefer an explicit per-offer lookup.

REST example

Python, using x402[httpx]. Replace EVM_SIGNER_KEY with your Base wallet’s private key (any EVM signer that holds USDC on Base).
import os
import httpx
from x402.http.clients.httpx import x402HttpxClient
from x402.mechanisms.evm import EthAccountSigner

signer = EthAccountSigner(private_key=os.environ["EVM_SIGNER_KEY"])

with x402HttpxClient(signer=signer) as client:
    response = client.post(
        "https://api.markdown2pdf.ai/markdown",
        json={
            "data": {"text_body": "# Hello from X402"},
            "options": {"document_name": "hello.pdf"},
        },
    )
    response.raise_for_status()
    print("Paid tx proof:", response.headers.get("X-Payment-Response"))
    job = response.json()
    print("Poll path:", job["path"])
The x402HttpxClient handles the 402 → sign → retry loop for you. If you want to do it manually (e.g. to customise the flow), the recipe is:
import base64, json, httpx
from x402.http.utils import encode_payment_signature_header

# 1. First request returns 402
r = httpx.post(url, json=payload)
assert r.status_code == 402
accepts = r.json()["offers"][1]["metadata"]["x402"]["accepts"][0]  # x402 offer

# 2. Build + sign a PaymentPayload using the scheme's client-side helper
# (see x402 docs for ExactEvmClientScheme.build_payment_payload)
payment_header = encode_payment_signature_header(signed_payload)

# 3. Re-POST with X-PAYMENT
r = httpx.post(url, json=payload, headers={"X-PAYMENT": payment_header})
r.raise_for_status()

When to pick X402 vs L402

Pick L402 if...

Your agent already holds sats and/or uses Lightning wallets such as Alby, LNbits, or Fewsats. Lightning settlement is instant and fees are negligible. See the L402 page.

Pick X402 if...

Your agent already holds USDC on Base, or you want dollar-denominated predictability. X402 is well-supported by the Coinbase CDP SDK and by x402-axios / x402-fetch on the TypeScript side.
Either way, the document generation backend is identical — the choice is purely about how you want to pay.