Skip to content

Concepts

What HTTP 402 Payment Required means and how x402 uses it

By Requestway · · 8 min read

HTTP 402 Payment Required is the status code a server returns when it wants to be paid before it serves a resource. For most of HTTP's history it was "reserved for future use" with no defined format, so almost nothing used it. The x402 protocol gives it a concrete meaning: a 402 that tells the client exactly what to pay, in which token, to which address, and how to retry.

This article covers what 402 means, why it went unused, what a 402 response looks like today, how a client should handle one, and how it differs from 401 and 403.

What the 402 status code means

402 is a 4xx status: the server is saying the request cannot be served as sent, and something on the client's side has to change. For 402, that something is payment. The resource exists, the server understood the request, and it will serve it once the client pays.

The reason phrase is "Payment Required". The HTTP/1.1 specification included it alongside 401 and 403 but did not define how a client should pay, what the response should contain, or how to retry with proof of payment. It simply marked the code as reserved for future use, and the current HTTP semantics specification still does.

So a bare 402 has always meant "pay first" with no shared answer to "pay how?"

Why 402 went unused for so long

The status code was the easy part. Everything around it was missing.

  • No standard way to pay inside HTTP. Paying on the web meant accounts, card forms, checkout redirects and a human clicking "buy". None of that fits in a request header that a program can read and answer.
  • No machine-readable price. A client receiving 402 had nothing to parse: no amount, no currency, no destination. It could only show an error.
  • Small payments did not pay. Card payments carry fixed per-transaction costs, which makes charging a fraction of a cent per request impractical. Per-request pricing needed a payment rail where tiny amounts make sense.
  • Accounts solved it well enough. Servers that wanted money issued API keys tied to billing accounts and answered unpaid or unauthenticated requests with 401 or 403. The payment happened out of band, monthly, and HTTP never had to know.

The result was a status code everyone could name and few could use. Some APIs return 402 informally for billing problems, each with its own body format, but a generic client has no way to act on those.

What changed: x402

Two things made a real 402 workable. Stablecoins such as USDC can move small, exact amounts on chains like Base, and token standards such as EIP-3009 let a payer sign a transfer authorization offline that someone else can submit. A client can pay without an account, and the server can check the payment without holding any keys.

x402 is an open protocol that defines the missing parts on top of HTTP: what the 402 contains, how the client sends payment, and what receipt comes back. The spec and reference SDKs are at github.com/x402-foundation/x402. For a broader introduction, see What is x402.

In version 2 of the protocol, the exchange uses three headers, all carrying base64-encoded JSON:

Header Direction Carries
PAYMENT-REQUIRED server to client, on the 402 what the server accepts as payment
PAYMENT-SIGNATURE client to server, on the retry the signed payment
PAYMENT-RESPONSE server to client, on the 200 the settlement receipt

Version 1, still seen in the wild, put the requirements in the 402 body, the payment in X-PAYMENT, and the receipt in X-PAYMENT-RESPONSE.

What a 402 response looks like today

Here is an unpaid request to an x402-protected endpoint:

GET /market-data HTTP/1.1
Host: api.example.com
Accept: application/json

And the response:

HTTP/1.1 402 Payment Required
Content-Type: application/json
Cache-Control: no-store, private
PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MiwiZXJyb3IiOiJQQVlNRU5ULVNJR05B…

The header value is truncated here. Decoded from base64, it is a JSON object like this:

{
  "x402Version": 2,
  "error": "PAYMENT-SIGNATURE header is required",
  "resource": {
    "url": "https://api.example.com/market-data",
    "description": "Live market data",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "amount": "10000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0xYourAddress",
      "maxTimeoutSeconds": 60,
      "extra": { "name": "USD Coin", "version": "2" }
    }
  ],
  "extensions": {}
}

Reading the accepts entry field by field:

  • scheme: exact means pay exactly this amount. On EVM chains it uses an EIP-3009 transferWithAuthorization signed with EIP-712.
  • network: a CAIP-2 chain id. eip155:8453 is Base; eip155:84532 is the Base Sepolia testnet.
  • amount: a string in the token's atomic units. USDC has 6 decimals, so 10000 is $0.01 and 1000000 is $1.00.
  • asset: the token contract, here USDC on Base.
  • payTo: the address that receives the funds. The server needs only this address, never a private key.
  • maxTimeoutSeconds: how long the client has to complete payment.
  • extra: the token's EIP-712 domain name and version, which the client needs to build a valid signature.

accepts is a list. A server that takes payment on several networks lists each, and the client picks one it can pay.

The body is up to the server. The reference implementation sends {}; the Laravel package sends the same v2-shaped JSON with a human-readable error, so a person who opens the URL in a browser sees why they got a 402. The header is the canonical place to read from.

Note the Cache-Control. A 402 should not be cached by shared caches: the requirements can change, and the response is specific to the request.

The paid retry and the receipt

The client signs a payment matching one accepts entry and repeats the same request with the signature attached:

GET /market-data HTTP/1.1
Host: api.example.com
PAYMENT-SIGNATURE: eyJ4NDAyVmVyc2lvbiI6Miwi…

The server checks the payment locally and with a facilitator's /verify, runs the handler, settles through the facilitator's /settle, and answers:

HTTP/1.1 200 OK
Content-Type: application/json
PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbiI6…

Decoded, the receipt says whether settlement succeeded, the transaction hash, who paid and on which network:

{
  "success": true,
  "transaction": "0x…",
  "network": "eip155:8453",
  "payer": "0x…"
}

The funds moved directly from the payer's wallet to payTo. The facilitator submitted the transfer and paid gas, but it never held the money and could not change the amount or recipient, because both are inside what the payer signed. How AI agents pay for APIs walks through the signature in detail.

How clients should handle a 402

A client that meets a 402 should work through it in this order.

  1. Look for PAYMENT-REQUIRED. If it is present, decode it as base64 JSON. If it is missing, check the body for a version 1 object. If neither is there, the server is using 402 informally and there is nothing standard to act on.
  2. Pick an accepts entry you can pay. Match scheme and network to what your wallet supports and holds funds on.
  3. Check the price against a budget. Convert amount from atomic units and refuse anything above your limit. An agent should never pay whatever it is asked.
  4. Sign and retry. Build the payment for that entry and resend the original request with PAYMENT-SIGNATURE (or X-PAYMENT for a version 1 server).
  5. Read the receipt. Decode PAYMENT-RESPONSE and keep the transaction for your records.

In TypeScript, the official @x402/fetch package wraps fetch so that decoding the 402, signing and retrying happen for you, and the last two lines read the receipt:

import { wrapFetchWithPaymentFromConfig, decodePaymentResponseHeader } from '@x402/fetch'
import { ExactEvmScheme } from '@x402/evm'
import { privateKeyToAccount } from 'viem/accounts'

const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`)
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{ network: 'eip155:84532', client: new ExactEvmScheme(account) }],
})

const response = await fetchWithPayment('https://api.example.com/premium-data')
const receipt = response.headers.get('PAYMENT-RESPONSE')
if (receipt) console.log(decodePaymentResponseHeader(receipt))

This example is configured for Base Sepolia; for Base mainnet use eip155:8453, or eip155:* to support all EVM chains. Use a dedicated low-balance wallet for agents, so a bug or a bad endpoint can only spend what you put there.

When the retry fails

A paid retry can still fail. With x402 servers such as the Laravel package, the status tells you where:

Status Meaning What to do
400 the payment header could not be decoded fix the client's encoding
402 again the payment was rejected at verification or settlement read the error, sign a new payment if appropriate
502 the server could not reach its facilitator retry later

Never resend the same signed payment. Each authorization has a unique nonce and a validity window (validAfter, validBefore), the token contract will not execute it twice, and servers reject a reused payload with invalid_payload ("This payment has already been used. Sign a new one."). Common rejection reasons include insufficient_funds, invalid_exact_evm_payload_authorization_value_mismatch for an underpayment, and the valid_after and valid_before errors for clock or timing problems. The failure guide covers each one.

402 vs 401 vs 403

The three are easy to confuse because they all mean "not like this". They differ in what the client can do about it.

401 Unauthorized 402 Payment Required 403 Forbidden
Meaning no valid authentication payment needed before serving understood, and refused
Client fixes it by sending credentials paying usually nothing it can send
Standard header WWW-Authenticate PAYMENT-REQUIRED (with x402) none
Retry with an Authorization header with PAYMENT-SIGNATURE not with the same identity
Needs prior credentials yes no n/a

A useful rule: 401 asks who you are, 402 asks you to pay, 403 says no. Despite its name, 401 is about authentication. 403 means the server knows enough to refuse, and changing credentials or paying will not help.

The two can coexist on one route. With the Laravel package, ->middleware('x402:0.25,free-for-auth') serves signed-in users free and returns 402 to everyone else, and an API key in X-API-Key (configured with X402_API_KEYS) can bypass payment for customers who already pay another way. The comparison with API keys and subscriptions covers when to use which.

Returning 402 from your own API

If you want your API to return a proper x402 402, you do not need to build the headers yourself. In Laravel, requestway/laravel-x402 does it with one line:

Route::get('/market-data', MarketDataController::class)->middleware('x402:0.01');

For Node, the official @x402/express, @x402/next and @x402/hono packages do the same. The install guide covers both, and the endpoint tester calls your route without paying, checks that the 402 contains everything a client needs (scheme, network, asset, payTo, and the EIP-712 name and version in extra), then pays it with test USDC on a testnet and shows you the receipt.

FAQ

What does HTTP error 402 mean?

It means the server wants payment before it will serve the resource. HTTP reserved the code for future use without defining how to pay; the x402 protocol fills that in with a PAYMENT-REQUIRED header describing the price, token, network and recipient.

Is 402 an error I should retry?

Only after paying. Retrying the same request without payment returns the same 402. An x402 client signs a payment and retries with a PAYMENT-SIGNATURE header.

What is the difference between 402 and 403?

402 means you can get the resource by paying. 403 means the server refuses the request, and neither paying nor changing the request will help.

Can a browser handle a 402 Payment Required response?

A browser shows it as an error page with whatever body the server sent. x402 is designed for programmatic clients such as AI agents that hold a wallet; for people in a browser, a normal checkout is the better fit.

Where is the payment information in an x402 402 response?

In version 2, in the PAYMENT-REQUIRED response header as base64-encoded JSON. Version 1 servers put it in the response body instead.