Docs/Concepts

The x402 protocol

x402 is an open HTTP-native micropayment protocol. The name comes from the HTTP status code 402 Payment Required, which was reserved in the original HTTP/1.1 spec but left unspecified for 25 years.

The wire protocol in one page

An x402 payment is four HTTP messages with a specific header dance.

Request 1 — plain GET

http
GET /article/42 HTTP/1.1
Host: news.example.com

Response 1 — 402 with a quote

http
HTTP/1.1 402 Payment Required
PAYMENT-REQUIRED: <base64(body)>
Content-Type: application/json

{
"x402Version": 2,
"accepts": [{
  "scheme": "exact",
  "network": "solana-devnet",
  "asset": "<spl-mint>",
  "amount": "10000",
  "payTo": "<merchant-eta-address>",
  "resource": "https://news.example.com/article/42",
  "maxTimeoutSeconds": 300,
  "mimeType": "application/json"
}]
}

Request 2 — retry with a signed payment

http
GET /article/42 HTTP/1.1
Host: news.example.com
PAYMENT-SIGNATURE: <base64(envelope)>

Response 2 — 200 + data + settlement receipt

http
HTTP/1.1 200 OK
X-Payment-Response: <base64(settlement)>
Content-Type: application/json

{ "id": 42, "body": "..." }

That's the whole protocol on the wire. Four messages. Three headers: PAYMENT-REQUIRED (quote), PAYMENT-SIGNATURE (proof-of-payment), X-Payment-Response (settlement receipt).

What makes x402 different

  • HTTP-native. Not a sidecar service, not a webhook callback — plain request/response. curl + jq can debug it.
  • Chain-agnostic at the spec level. The accepts[].network field picks the chain. Solana is one of several adapters.
  • No-data-no-pay invariant. A signed payment is bound to a specific resource URL + amount + recipient. If the merchant never serves data, the merchant also doesn't get a verifiable payment to claim.

How Obscura extends x402 with confidentiality

Obscura's flavour of x402 swaps the classic "facilitator settles a signed SPL transfer" pattern for a confidential mixer transfer:

  • The agent never holds a Solana key — Obscura's backend signs on its behalf inside /api/x402/sign.
  • The transfer is a deposit + commitment in the on-chain Umbra mixer tree, not a direct SPL transfer. The amount is encrypted, and the on-chain link between sender ETA and recipient ETA is broken by the mixer.
  • The payment header is an umbra-mixer-v1 envelope carrying the queue + callback signatures. The merchant verifies these on-chain via standard Solana RPC — no facilitator involvement.

Why Solana

  • Sub-cent transaction fees (a fraction of a lamport per transfer) — the only L1 where a $0.001 API call doesn't get eaten by gas.
  • Fast confirmations. The merchant's verification is two getTransaction reads adding ~150–400ms; on Ethereum L1 the same flow would add 12+ seconds per call.
  • Fee-payer decoupling. Solana lets a third party pay the network fee, which is what lets Obscura's backend fund the queue + callback txns from a treasury — your agent only ever needs the configured stablecoin.

Replay protection

Two defences in the Obscura flow:

  • Backend-side fingerprint dedupe. /api/x402/sign rejects duplicate spend intents (same agent + URL + amount + recipient ETA
    • asset) while the original is in flight, returning conflict (409).
  • Merchant-side replay window. The merchant SDK caches each queueSignature in-memory and rejects re-presentation of the same signature within replayWindowMs (default 5 minutes).

Beyond that, the on-chain Umbra program enforces nullifier uniqueness: the same UTXO can't be claimed twice, and the same encrypted-balance deduction can't be replayed at the program level.

Further reading