History of HTTP 402
HTTP 402 Payment Required was defined in the original HTTP/1.1 specification (RFC 2616) back in 1999. The specification reserved it "for future use" with the expectation that a digital payment mechanism would eventually emerge.
"This code is reserved for future use. The initial aim of creating this code was for using it for some form of digital cash or micropayment scheme, but that has not happened, and this code is not usually used."
— RFC 2616, Section 10.4.3
For over two decades, 402 remained largely unused because there was no standardized way to specify payment details or verify payments programmatically. Blockchain technology changed this by providing a trustless, programmable payment layer.
The x402 Protocol
x402 is a protocol specification that defines how to implement HTTP 402 using blockchain payments. It standardizes:
- How servers communicate payment requirements
- How clients prove payment was made
- How servers verify payments
- How to handle edge cases and errors
Request Flow
Here's the complete flow of an x402 payment:
Step 1: Initial Request
The client makes a standard HTTP request to a protected resource:
GET /api/premium/data HTTP/1.1
Host: api.example.com
Accept: application/jsonStep 2: Payment Challenge
The server responds with 402 and a payment challenge in the WWW-Authenticate header:
HTTP/1.1 402 Payment Required
WWW-Authenticate: x402 price="0.001"
currency="ETH"
network="base"
recipient="0x742d35Cc6634C0532925a3b844Bc4e7595f0aB42"
challenge="vnt_ch_abc123..."
expires="1704067200"
Content-Type: application/json
{
"error": "payment_required",
"message": "Payment of 0.001 ETH required to access this resource",
"paymentDetails": {
"price": "0.001",
"currency": "ETH",
"network": "base",
"recipient": "0x742d35Cc6634C0532925a3b844Bc4e7595f0aB42"
}
}Step 3: Payment Execution
The client parses the challenge, sends payment on-chain, and waits for confirmation:
// Client-side
const challenge = parseChallenge(response.headers.get('WWW-Authenticate'))
// Send payment transaction
const tx = await wallet.sendTransaction({
to: challenge.recipient,
value: ethers.parseEther(challenge.price),
data: encodePaymentData(challenge.challenge),
})
// Wait for confirmation
const receipt = await tx.wait()Step 4: Authenticated Request
The client retries the request with payment proof:
GET /api/premium/data HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: x402 challenge="vnt_ch_abc123..."
txHash="0x1234567890abcdef..."
signature="0xdeadbeef..."Step 5: Verification and Response
The server verifies the payment on-chain and returns the resource:
HTTP/1.1 200 OK
X-Vanta-Receipt: eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9...
X-Vanta-Token: vanta_tk_xyz789...
Content-Type: application/json
{
"data": "This is the premium content you paid for",
"paidAmount": "0.001 ETH",
"txHash": "0x1234567890abcdef..."
}Challenge Format
The WWW-Authenticate header contains all information needed for payment:
| Field | Description | Example |
|---|---|---|
price | Amount to pay | 0.001 |
currency | Payment currency | ETH |
network | Blockchain network | base |
recipient | Wallet address | 0x742d... |
challenge | Unique challenge ID | vnt_ch_abc123 |
expires | Unix timestamp | 1704067200 |
memo | Optional description | API Access |
Authorization Format
The Authorization header proves payment:
| Field | Description |
|---|---|
challenge | The challenge ID from the 402 response |
txHash | Transaction hash of the payment |
signature | Signature proving ownership of sender address |
Payment Verification
The server verifies payments by checking:
- Challenge validity: Challenge exists and hasn't expired
- Transaction existence: Transaction exists on the specified network
- Amount: Payment amount matches or exceeds the challenge price
- Recipient: Payment was sent to the correct address
- Data: Transaction data contains the challenge ID
- Confirmation: Transaction has sufficient confirmations
Performance Note
Error Codes
| Error | Description | Client Action |
|---|---|---|
INVALID_CHALLENGE | Challenge expired or not found | Request new challenge |
PAYMENT_NOT_FOUND | Transaction not on chain | Wait and retry |
INSUFFICIENT_PAYMENT | Amount too low | Send additional payment |
WRONG_RECIPIENT | Wrong address | Send new payment |
INVALID_SIGNATURE | Signature verification failed | Re-sign with correct key |
Next Steps
- Payment Challenges - Deep dive into challenge generation
- Verification - How payment verification works
- Request Lifecycle - Complete flow diagram