x402 HTTP 402 Semantics
The HTTP 402 Payment Required status code is the core payment signal in the ItPay Protocol. This guide explains how ItPay implements the x402 standard and how agents and clients handle 402 responses.
1. What is x402?
x402 is an open, network-agnostic payments protocol built natively on HTTP. It uses the 402 Payment Required status code to signal that a requested resource requires payment before it can be delivered.
Key principles:
- Transport-native — no separate payment API calls, just HTTP headers and status codes
- Scheme-agnostic — supports crypto (EVM, SVM, Stellar) and fiat payment channels
- Minimal friction — one middleware line on the server, one function on the client
- Permissionless — any resource server can participate without gatekeeping
ItPay extends x402 with QR-code-based consumer payment channels (Alipay, WeChat, PromptPay, etc.) so that AI agents can request payment from humans using the most ubiquitous payment interface in the world.
2. ItPay's 402 Implementation
When an Agent API endpoint requires payment before serving the resource, it responds with HTTP 402 Payment Required and a set of payment headers that describe the debt, the accepted payment channels, and a QR code for human checkout.
Response Headers
| Header | Value | Description |
|---|---|---|
X-Payment-Intent | pi_2xK3m9QrL8vN5pW1 | |
X-Payment-Channel | alipay | The payment channel the payer should use (e.g. alipay, wechat, promptpay) |
X-Payment-Amount | USD 5.00 | Amount and currency required, as a human-readable string |
X-Payment-QR | itpay://pay/pi_2xK3m9... |
Full HTTP Response Example
HTTP/1.1 402 Payment Required
Content-Type: application/json
X-Payment-Intent: pi_2xK3m9QrL8vN5pW1
X-Payment-Channel: alipay
X-Payment-Amount: USD 5.00
X-Payment-QR: itpay://pay/pi_2xK3m9QrL8vN5pW1?channel=alipay&amount=5.00¤cy=USD
{
"error": "payment_required",
"message": "Payment is required to access this resource.",
"payment_intent": {
"id": "pi_2xK3m9QrL8vN5pW1",
"amount": {
"value": "5.00",
"currency": "USD"
},
"channel": "alipay",
"qr_uri": "itpay://pay/pi_2xK3m9QrL8vN5pW1?channel=alipay&amount=5.00¤cy=USD",
"expires_at": "2026-05-27T12:05:00Z"
}
}
The JSON body mirrors the headers for clients that prefer structured parsing over header inspection. Either can be used — the headers are the canonical source of truth.
Multiple Channel Support
A resource server may accept multiple payment channels. In this case, the response repeats the channel-specific headers:
HTTP/1.1 402 Payment Required
Content-Type: application/json
X-Payment-Intent: pi_2xK3m9QrL8vN5pW1
X-Payment-Channel: alipay
X-Payment-Channel: wechat
X-Payment-Channel: promptpay
X-Payment-Amount: USD 5.00
{
"error": "payment_required",
"payment_intent": {
"id": "pi_2xK3m9QrL8vN5pW1",
"amount": { "value": "5.00", "currency": "USD" },
"channels": [
{ "channel": "alipay", "qr_uri": "itpay://pay/...?channel=alipay" },
{ "channel": "wechat", "qr_uri": "itpay://pay/...?channel=wechat" },
{ "channel": "promptpay", "qr_uri": "itpay://pay/...?channel=promptpay" }
],
"expires_at": "2026-05-27T12:05:00Z"
}
}
3. The 402 Payment Flow
The complete payment lifecycle follows a request → 402 → checkout → payment → retry → success pattern:
┌──────────┐ ┌──────────────┐ ┌──────────┐
│ Client │ │ Agent API │ │ Human │
│ (Agent) │ │ (Merchant) │ │ (Payer) │
└────┬─────┘ └──────┬───────┘ └────┬─────┘
│ │ │
│ 1. GET /api/report │ │
│─────────────────────▶│ │
│ │ │
│ 2. 402 Payment Req. │ │
│ X-Payment-Intent │ │
│ X-Payment-Channel │ │
│ X-Payment-Amount │ │
│ X-Payment-QR │ │
│◀─────────────────────│ │
│ │ │
│ 3. Route to checkout │ │
│ (redirect / show QR) │ │
│──────────────────────────────────────────────▶│
│ │ │
│ │ 4. Scan QR │
│ │ & complete pay │
│ │◀─────────────────────│
│ │ │
│ 5. Retry with │ │
│ X-Payment-Proof │ │
│─────────────────────▶│ │
│ │ │
│ 6. 200 OK + Resource │ │
│◀─────────────────────│ │
│ │ │
┌────┴─────┐ ┌──────┴───────┐ ┌────┴─────┐
│ Client │ │ Agent API │ │ Human │
│ (Agent) │ │ (Merchant) │ │ (Payer) │
└──────────┘ └──────────────┘ └──────────┘
Step-by-step:
-
Client requests — An AI agent or HTTP client calls a protected API endpoint (e.g.
GET /api/report). -
Agent returns 402 — The resource server responds with
402 Payment Requiredand payment headers. The client receives the payment intent details. -
Route to checkout — The client (or a UI layer) displays the QR code or redirects the human payer to the payment checkout page. The
X-Payment-QRheader contains the scannable URI. -
Human pays — The human scans the QR code with their wallet app (Alipay, WeChat, PromptPay, etc.) and completes the payment.
-
Retry with proof — The client retries the original request, attaching the payment proof via the
X-Payment-Proofheader. -
Success — The resource server validates the proof, serves the resource with
200 OK.
GET /api/report HTTP/1.1
Authorization: Bearer ***
X-Payment-Proof: pi_2xK3m9QrL8vN5pW1
4. Auto-Pay (Under Limit)
For low-value payments, the protocol supports automatic payment without user intervention. When a payer has configured an auto_pay_limit, the ItPay client or gateway can complete payments below that threshold automatically — no QR scan, no human redirect, no interruption.
How It Works
- The client receives a
402 Payment Requiredresponse. - It checks the
X-Payment-Amountvalue against the configuredauto_pay_limit. - If amount ≤ auto_pay_limit: the client auto-completes the payment using the payer's pre-authorized wallet or channel, then retries the original request with the proof header.
- If amount > auto_pay_limit: the client routes the payer to the standard checkout / QR flow.
Configuration
The auto_pay_limit is configured at install time through the Install endpoint. When an agent registers itself or a payer installs an agent's service, they can specify spending limits:
{
"service_id": "svc_weather_001",
"payer": {
"id": "user_frank",
"channel": "alipay",
"wallet_id": "alipay_user_***"
},
"auto_pay_limit": {
"value": 1.00,
"currency": "USD"
},
"spending_limits": {
"daily": { "value": 10.00, "currency": "USD" },
"monthly": { "value": 50.00, "currency": "USD" }
}
}
auto_pay_limit— Maximum single-payment amount that can be auto-charged. Payments at or below this value complete silently.spending_limits.daily— Caps total auto-paid spending per rolling 24-hour window.spending_limits.monthly— Caps total auto-paid spending per calendar month.
Payer Experience
With auto-pay enabled for low-value requests, the human payer experiences no interruption:
┌──────────┐ ┌──────────────┐
│ Client │ │ Agent API │
│ (Agent) │ │ (Merchant) │
└────┬─────┘ └──────┬───────┘
│ │
│ GET /api/report │
│─────────────────────▶│
│ │
│ 402 ($0.50 ≤ $1.00) │
│◀─────────────────────│
│ │
│ Auto-pay $0.50 via │
│ pre-authorized wallet│
│ (no user interaction)│
│ │
│ Retry with proof │
│─────────────────────▶│
│ │
│ 200 OK + Resource │
│◀─────────────────────│
│ │
┌────┴─────┐ ┌──────┴───────┐
│ Client │ │ Agent API │
│ (Agent) │ │ (Merchant) │
└──────────┘ └──────────────┘
5. Client-Side Example (TypeScript)
Below is a complete TypeScript example showing how a client handles the 402 flow: catches the 402, routes to payment if needed, and retries with proof.
import { randomUUID } from 'node:crypto';
interface PaymentIntent {
id: string;
amount: { value: string; currency: string };
channel: string;
qr_uri?: string;
expires_at: string;
}
interface PaymentResponse {
error: string;
payment_intent: PaymentIntent;
}
/**
* Fetch a resource with automatic 402 handling.
* - If the response is 200: return the resource.
* - If the response is 402: extract payment details, route to payment,
* then retry the original request with proof.
*/
async function fetchWithPayment(
url: string,
options: RequestInit = {}
): Promise<Response> {
// 1. Initial request
let response = await fetch(url, {
...options,
headers: {
...(options.headers as Record<string, string>),
'X-Request-Id': randomUUID(),
},
});
// 2. Handle 402 Payment Required
if (response.status === 402) {
const body: PaymentResponse = await response.json();
const intent = body.payment_intent;
const paymentIntent = response.headers.get('X-Payment-Intent') || intent.id;
const amount = response.headers.get('X-Payment-Amount')
|| `${intent.amount.currency} ${intent.amount.value}`;
const channel = response.headers.get('X-Payment-Channel') || intent.channel;
const qrUri = response.headers.get('X-Payment-QR') || intent.qr_uri;
console.log(`Payment required: ${amount} via ${channel}`);
// 3. Determine payment strategy
const autoPayLimit = getAutoPayLimit(); // from user config
const amountValue = parseFloat(intent.amount.value);
if (autoPayLimit && amountValue <= autoPayLimit.value
&& autoPayLimit.currency === intent.amount.currency) {
// Auto-pay: complete payment silently
console.log(`Auto-paying ${amount} (under limit of ${autoPayLimit.value})`);
await completePayment(intent.id, channel);
} else {
// Route to human checkout: display QR code or redirect
if (qrUri) {
console.log(`Redirecting to payment checkout...`);
await displayCheckout(intent.id, qrUri, amount, channel);
} else {
throw new Error('No payment QR URI provided');
}
}
// 4. Retry with payment proof
response = await fetch(url, {
...options,
headers: {
...(options.headers as Record<string, string>),
'X-Payment-Proof': paymentIntent,
'X-Request-Id': randomUUID(),
},
});
if (!response.ok) {
throw new Error(`Payment retry failed: ${response.status}`);
}
}
return response;
}
/**
* Complete a payment using the pre-authorized wallet/channel.
*/
async function completePayment(
intentId: string,
channel: string
): Promise<void> {
const response = await fetch(`https://api.itpay.network/v1/payments/${intentId}/complete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel }),
});
if (!response.ok) {
throw new Error(`Auto-pay failed: ${response.status} ${await response.text()}`);
}
}
/**
* Present the payment checkout to the human payer.
*/
async function displayCheckout(
intentId: string,
qrUri: string,
amount: string,
channel: string
): Promise<void> {
// In a browser environment, redirect or render a QR code
if (typeof window !== 'undefined') {
window.open(qrUri, '_blank');
}
// Wait for payment confirmation (polling)
await pollPaymentStatus(intentId);
}
/**
* Poll until the payment intent is confirmed.
*/
async function pollPaymentStatus(
intentId: string,
maxRetries = 60,
intervalMs = 1000
): Promise<void> {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(
`https://api.itpay.network/v1/payments/${intentId}/status`
);
const status = await response.json();
if (status.status === 'completed' || status.status === 'settled') {
return;
}
if (status.status === 'expired' || status.status === 'failed') {
throw new Error(`Payment ${intentId} ended with status: ${status.status}`);
}
await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
throw new Error(`Payment ${intentId} timed out after ${maxRetries * intervalMs}ms`);
}
/**
* Read the auto-pay limit from user configuration.
* This would typically come from local storage, user settings, or env vars.
*/
function getAutoPayLimit(): { value: number; currency: string } | null {
// Example: read from environment or config store
const raw = process.env.ITPAY_AUTO_PAY_LIMIT;
if (!raw) return null;
const [value, currency] = raw.split(' ');
return { value: parseFloat(value), currency };
}
// --- Usage ---
async function main() {
try {
const response = await fetchWithPayment('https://api.agent.com/v1/reports/weather', {
headers: {
Authorization: 'Bearer YOUR_API_KEY',
},
});
const data = await response.json();
console.log('Resource received:', data);
} catch (error) {
console.error('Payment flow failed:', error);
}
}
main();
Key Behaviors
| Scenario | Behavior |
|---|---|
| 200 OK | Resource is returned directly, no payment needed |
| 402 + under auto_pay_limit | Payment auto-completes; user never sees a prompt |
| 402 + over auto_pay_limit | QR checkout is displayed; user must scan to pay |
| 402 + payment times out | Error is thrown after configurable max wait |
| Retry with invalid proof | 402 is returned again (proof expired or invalid) |
Related
- PaymentIntent — The state machine that tracks each payment lifecycle
- QR Charge — QR code payment object for consumer wallet apps
- Architecture: Two-Layer Design — How x402 fits into the overall protocol
- Install Endpoint — Configure auto-pay limits and spending caps
- x402 on GitHub — The open standard specification