understanding the x402 protocol
the internet has had a status code for payments since 1997. HTTP 402 — Payment Required. it's been sitting in the spec for almost three decades, marked "reserved for future use."
the future showed up.
with AI agents making millions of api calls per day, machine-to-machine payments are no longer theoretical. an agent needs data, a service provides it, money changes hands. no human in the loop. no credit card form. no subscription plan. just pay per request.
x402 is the protocol that makes this work. and solana is the settlement layer that makes it fast enough to be practical.
---
the problem x402 solves
today, when an AI agent needs to access a paid api, the options are limited:
none of these work for autonomous agents that need to discover and pay for services dynamically.
x402 flips the model. instead of pre-arranging payment, the agent simply makes the request. if payment is required, the server says so — in a machine-readable format — and the agent pays on the spot. no accounts, no api keys, no credit cards.
---
the http 402 response
when a server supports x402, it responds to unpaid requests with a standard HTTP 402 status code plus a set of payment headers. here's what that looks like on the wire:
``http
HTTP/1.1 402 Payment Required
Content-Type: application/json
X-Payment-Required: true
X-Payment-Amount: 1000000
X-Payment-Token: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
X-Payment-Token-Symbol: USDC
X-Payment-Token-Decimals: 6
X-Payment-Recipient: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
X-Payment-Network: solana
X-Payment-Chain: mainnet-beta
X-Payment-Memo: req_abc123
X-Payment-Expires: 1708300800
{
"error": "payment_required",
"message": "this endpoint requires payment",
"payment": {
"amount": 1000000,
"token": "USDC",
"recipient": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"network": "solana",
"memo": "req_abc123",
"expires": 1708300800
}
}
`
let's break down each header.
required headers
| header | description | example |
|---|---|---|
X-Payment-Required | signals this is an x402 response | true |
X-Payment-Amount | amount in smallest token unit (e.g., USDC has 6 decimals, so 1000000 = 1 USDC) | 1000000 |
X-Payment-Token | the SPL token mint address the server wants payment in | EPjFWdd5... |
X-Payment-Recipient | the solana address to send payment to | 7xKXtg2C... |
X-Payment-Network | the blockchain network | solana |
optional headers
| header | description | example |
|---|---|---|
X-Payment-Token-Symbol | human-readable token symbol | USDC |
X-Payment-Token-Decimals | decimal places for the token | 6 |
X-Payment-Chain | specific chain/cluster | mainnet-beta |
X-Payment-Memo | reference string to include in the payment transaction | req_abc123 |
X-Payment-Expires | unix timestamp after which the payment offer expires | 1708300800 |
the body is optional but recommended. it provides the same information in a structured json format for clients that prefer parsing json over headers. the headers are the canonical source.
---
the payment flow
the full x402 flow has six steps. five of them are automatic when using parasoldex.
step 1: initial request
the agent makes a normal api call. nothing special.
`
GET /api/premium-data HTTP/1.1
Host: api.example.com
Content-Type: application/json
`
step 2: 402 response
the server checks the request. no payment proof found. responds with 402 and payment details.
`
HTTP/1.1 402 Payment Required
X-Payment-Required: true
X-Payment-Amount: 500000
X-Payment-Token: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
X-Payment-Recipient: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
X-Payment-Memo: req_xyz789
`
step 3: parse and validate
the agent (or sdk) parses the payment headers. validates:
if any check fails, the agent aborts. no money moves.
step 4: execute payment
the agent constructs a solana transaction:
so the server can match the payment to the request`
Transaction:
- SPL Transfer: 500000 USDC → 7xKXtg2C...
- Memo: "req_xyz789"
- Signer:
`
on solana, this confirms in roughly 400 milliseconds. the agent barely pauses.
step 5: retry with proof
the agent retries the original request, now including proof of payment:
`
GET /api/premium-data HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-Payment-Tx: 5UfDuX...kQ9xm (solana transaction signature)
X-Payment-Memo: req_xyz789
`
step 6: receive data
the server verifies the transaction on-chain:
if everything checks out:
`
HTTP/1.1 200 OK
Content-Type: application/json
{
"data": { ... }
}
`
the agent gets its data. the service gets paid. no accounts. no api keys. no humans.
---
why solana
x402 is blockchain-agnostic in theory. in practice, the settlement layer matters enormously. here's why solana is the right fit.
speed
a payment that takes 12 minutes to confirm (ethereum L1) is not a payment — it's a hold. the api server has to either trust unconfirmed transactions (risky) or make the agent wait (unusable).
solana confirms transactions in ~400ms. the entire x402 flow — detect, pay, retry — completes in under 2 seconds. that's fast enough to feel synchronous.
cost
ethereum L1 gas fees can spike to $50+ per transaction during congestion. even L2s cost $0.01-0.10. for micropayments — paying $0.001 for an api call — the fee can't exceed the payment.
solana transaction fees are approximately 0.000005 SOL (~$0.001). you can make thousands of payments per day and spend less than a dollar on fees.
token ecosystem
solana's SPL token standard means any token can be used for x402 payments. the most common:
finality
solana's finality is optimistic but fast. a transaction is "confirmed" after 1-2 slots (~800ms) and "finalized" after 31 slots (~12.4 seconds). for x402 payments, confirmed is sufficient — the risk of rollback at that point is negligible.
servers can choose their confirmation threshold. most accept "confirmed" for small amounts and require "finalized" for larger payments.
---
the auto-swap problem
here's a scenario: your agent holds USDC. the server wants payment in SOL.
without auto-swap, the agent fails. it has money, just not the right kind.
parasoldex solves this with automatic token swapping. when the agent detects it doesn't hold the required token (or doesn't hold enough), it:
the agent sees all of this as a single await. the swap and payment happen atomically from the agent's perspective.
`typescript
// agent holds 10 USDC, server wants 0.1 SOL
// parasoldex automatically:
// 1. swaps USDC → SOL via best route
// 2. pays the 0.1 SOL
// 3. retries the request
const data = await parasol.fetch("https://api.example.com/data");
// data is here. no manual intervention.
`
---
supported tokens
x402 on solana supports any SPL token in theory. in practice, the tokens that work well for payments are:
tier 1 — widely accepted, high liquidity
| token | mint address | decimals | notes |
|---|---|---|---|
| SOL | So111...111112 | 9 | native, no token account setup needed |
| USDC | EPjFW...Dt1v | 6 | most common payment token |
| USDT | Es9vM...3aRs | 6 | alternative stablecoin |
tier 2 — usable with auto-swap
any SPL token with sufficient liquidity on jupiter can be used as a source for payments. parasoldex will swap it to whatever the server requires. this means your agent can hold a single token (say USDC) and pay any x402 request regardless of what token the server wants.
custom tokens
if you're building an x402 server, you can request payment in any SPL token. keep in mind that the more obscure the token, the fewer agents will be able to pay without swapping. USDC is the safest default for broad compatibility.
---
implementing an x402 server
if you want to accept x402 payments (not just make them), here's the minimal implementation:
`typescript
import express from "express";
import { Connection, PublicKey } from "@solana/web3.js";
const RECIPIENT = new PublicKey("your-wallet-address");
const PRICE_LAMPORTS = 1_000_000; // 1 USDC
const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const connection = new Connection("https://api.mainnet-beta.solana.com");
app.get("/api/premium-data", async (req, res) => {
const paymentTx = req.headers["x-payment-tx"];
const paymentMemo = req.headers["x-payment-memo"];
if (!paymentTx) {
const memo = req_${crypto.randomUUID().slice(0, 8)};
return res.status(402).json({
error: "payment_required",
payment: {
amount: PRICE_LAMPORTS,
token: USDC_MINT,
tokenSymbol: "USDC",
recipient: RECIPIENT.toString(),
network: "solana",
memo,
},
}).set({
"X-Payment-Required": "true",
"X-Payment-Amount": PRICE_LAMPORTS.toString(),
"X-Payment-Token": USDC_MINT,
"X-Payment-Token-Symbol": "USDC",
"X-Payment-Recipient": RECIPIENT.toString(),
"X-Payment-Network": "solana",
"X-Payment-Memo": memo,
});
}
// verify the payment on-chain
const tx = await connection.getTransaction(paymentTx, {
commitment: "confirmed",
maxSupportedTransactionVersion: 0,
});
if (!tx) {
return res.status(400).json({ error: "transaction not found" });
}
// verify amount, recipient, memo...
// (simplified — production code should check all fields)
return res.json({
data: { message: "here's your premium data" },
});
});
``
this is intentionally simplified. a production server should:
---
security considerations
x402 involves real money moving programmatically. a few things to keep in mind.
for agents (payers)
for servers (receivers)
---
the bigger picture
x402 is not just about paying for api calls. it's infrastructure for a new kind of internet — one where machines transact with each other at the speed of the network.
some of what this enables:
the web was built on free content subsidized by ads. the agent web won't have eyeballs to show ads to. payments are the native business model.
solana makes those payments fast enough and cheap enough to be invisible. parasoldex makes them automatic.
---
further reading
---
get access
parasoldex is in early access. if you're building agents that need to pay for services on solana, request an invite at parasol.so/access.
no wallet required. 30 seconds.
---
parasol — it sees what you can't.