x402: HTTP 402 reborn — internet-native payments, on Solana
x402 makes the unused HTTP 402 status code into an actual payment protocol. Here's the request/response flow, the headers, and how Solana settles the payment.
devrels.xyz/a/77short linkHTTP 402 has existed in the spec since 1997 with the description "reserved for future use." 28 years later, x402 (a Coinbase-published open spec) finally gives it a use: internet-native, on-chain settlement for API access.
It's simple, payment-agnostic at the spec level, and works cleanly on Solana with USDC. Here's the wire-level flow.
The flow
Client → Server GET /paid-endpoint
Server → Client HTTP/1.1 402 Payment Required
X-Payment-Required: {
"scheme": "exact",
"network": "solana-mainnet",
"asset": "USDC",
"amount": "0.01",
"payTo": "<merchant-solana-address>",
"resource":"/paid-endpoint",
"nonce": "<server-issued>",
"expires": "<unix-ts>"
}
Client signs + submits a payment tx to Solana
Client → Server GET /paid-endpoint
X-Payment: {
"scheme": "exact",
"txid": "<solana-signature>",
"nonce": "<from-above>"
}
Server verifies on Solana that the tx matches the requirement
Server → Client HTTP/1.1 200 OK
<the actual content>Two HTTP round-trips, one Solana tx. The endpoint becomes permissionless — no API keys, no signup, just "pay $0.01 and you get the response."
The headers spec
Two headers do all the work:
X-Payment-Required— sent by the server in the 402 response. JSON-encoded payment requirement (scheme, network, asset, amount, recipient, nonce, expiry).X-Payment— sent by the client in the retry request. JSON-encoded payment proof (scheme, txid, nonce).
Schemes are the extension point. "exact" means "a transaction transferring exactly this asset and amount to this address." Future schemes could be upTo (subscription-style), streaming (per-second metering), refundable (escrow-released), etc.
Solana settlement, concrete
For the scheme: "exact" on Solana with USDC, the client builds a normal SPL Token transfer with the nonce attached as a memo:
import { Connection, Transaction, PublicKey } from "@solana/web3.js"
import {
getOrCreateAssociatedTokenAccount, createTransferCheckedInstruction,
} from "@solana/spl-token"
import { createMemoInstruction } from "@solana/spl-memo"
const conn = new Connection("https://api.mainnet-beta.solana.com")
const USDC = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
// From the X-Payment-Required header:
const { payTo, amount, nonce } = paymentRequired
const fromAta = await getOrCreateAssociatedTokenAccount(
conn, payer, USDC, payer.publicKey,
)
const toAta = await getOrCreateAssociatedTokenAccount(
conn, payer, USDC, new PublicKey(payTo),
)
const tx = new Transaction()
.add(createTransferCheckedInstruction(
fromAta.address, USDC, toAta.address, payer.publicKey,
Math.floor(parseFloat(amount) * 1_000_000), // USDC has 6 decimals
6, [],
))
.add(createMemoInstruction(nonce, [payer.publicKey]))
const sig = await conn.sendTransaction(tx, [payer])
// Now retry the request:
const resp = await fetch("/paid-endpoint", {
headers: {
"X-Payment": JSON.stringify({ scheme: "exact", txid: sig, nonce }),
},
})Server-side verification
import { Connection, PublicKey, ParsedAccountData } from "@solana/web3.js"
async function verifyPayment(
conn: Connection,
xPayment: { txid: string; nonce: string },
requirement: { payTo: string; amount: string; nonce: string },
): Promise<boolean> {
if (xPayment.nonce !== requirement.nonce) return false
// Fetch the parsed tx
const tx = await conn.getParsedTransaction(xPayment.txid, "confirmed")
if (!tx || tx.meta?.err) return false
// Find the SPL transfer to our merchant address
const instructions = tx.transaction.message.instructions
const transfer = instructions.find((ix) => {
if (!("parsed" in ix)) return false
return ix.program === "spl-token" && ix.parsed.type === "transferChecked"
}) as any
if (!transfer) return false
const info = transfer.parsed.info
if (info.destination !== merchantUsdcAta) return false // pre-computed
const expected = Math.floor(parseFloat(requirement.amount) * 1_000_000)
if (parseInt(info.tokenAmount.amount) !== expected) return false
// Verify the memo contains the nonce
const memo = instructions.find((ix) => "parsed" in ix && ix.program === "spl-memo") as any
if (!memo || memo.parsed !== requirement.nonce) return false
return true
}Writing this verification yourself means running an RPC connection and handling settlement edge cases. In practice most merchants delegate it to a facilitator — a service that exposes /verify and /settle endpoints so the resource server stays chain-agnostic. PayAI (payai.network) runs the largest Solana facilitator after Coinbase and even fronts the gas for both sides.
Why this matters
Three concrete unlocks:
- API monetisation without signups. A scraper, an LLM agent, or a script can pay $0.001 per request without credit card forms or developer accounts. The endpoint remains stateless from an auth perspective.
- Agent payments by default. An AI agent with a wallet can call any x402-enabled API on the open internet without needing pre-arranged credentials. This is the payment layer most agentic web demos have been missing.
- Micropayments that actually work. Solana's sub-cent fees make sub-cent API charges viable for the first time. $0.0001 per request is real, not theoretical.
The honest read
x402 won't replace API keys for B2B. Enterprise APIs need contracts, SLAs, abuse rate-limiting, identity. x402 fits the opposite niche: permissionless, agent-friendly, low-stakes API access where the cost of signup > the value of the call.
It also adds a Solana tx to every API call — ~400ms minimum, even with optimistic confirmation. For latency-sensitive paths you'd batch payments (pay $1.00 upfront for 1000 calls). For occasional or one-off calls, the 400ms is acceptable.
References
- x402.org — spec
- coinbase/x402 — reference SDKs
- PayAI — the x402 facilitator that verifies & settles for you (payai.network)
- Solana Pay — the related QR/URL payment spec
HTTP 402 was waiting 28 years for the right substrate. With sub-cent USDC transfers on Solana, it finally has one.
Keep reading
Every API you use today requires an account, a key, a billing portal, and a rotation policy. x402 collapses that to one HTTP header. pay.sh implements it as a single binary that wraps curl — 76 providers across AI, data, compute, messaging, finance, and more. No keys to manage. No subscriptions.
Both x402 and Mastercard's AP4M answer the same question — how does an agent pay without a human in the loop — and they answer it differently. One is an open HTTP status code settled in USDC on Solana; the other is a permissioned network that uses public chains only as a credential ledger. Here's the head-to-head, dimension by dimension, and a decision guide.
x402 needs someone to verify and settle payments — that's the facilitator. PayAI runs the biggest one after Coinbase, covers gas for both buyer and merchant, and lets a resource server stay completely chain-agnostic. Protocol view.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.
