Skip to main content
HomeLanding pagesHow to implement the x402 protocol
LANDING PAGE

How to implement the x402 protocol

8 min read·Last updated June 2, 2026

x402 is a 402-then-retry handshake. The server returns HTTP 402 with a body of version 1, a resource, and an accepts list of exact-usdc requirements. The payer reads it with parse402Response, settles, and retries with an X-Payment: exact-usdc:<base64> header the client builds. The server decodes it with parsePaymentHeader and verifies via paymentRequests.settle. Helpers live in @blockchain0x/x402.

What x402 is

x402 is a payment handshake built on the HTTP 402 Payment Required status. A resource that costs money answers an unpaid request with a 402 that quotes a price; the caller pays and retries the same request with a header proving payment; the resource verifies and serves. That is the whole protocol. It is designed for machine callers, which is why it fits agent traffic: there is no redirect to a checkout page, no session, and no human in the loop, just a status code and a header a program can handle on its own.

Most of the time you do not implement x402 yourself. On the pay side, createX402Client from @blockchain0x/x402 runs the handshake; on the receive side, the Fastify and Express adapters do, as in how-to-add-x402-to-my-api. This page is for when you are on a stack the adapters do not cover, or when you want to understand exactly what the wire looks like. The payment API product page is the broader reference.

The handshake in four steps

The protocol is four steps, and everything else is detail.

  1. The caller requests a paid resource without paying.
  2. The server responds 402 with a body listing what it accepts: the exact-usdc scheme, the network, the payToAddress, and the amount.
  3. The caller settles the payment in USDC and retries the same request with an X-Payment header carrying proof.
  4. The server verifies that header against the requirement and, if it holds, serves the response.

Steps 2 and 4 are the receiver's job; steps 1 and 3 are the payer's. The wire helpers in @blockchain0x/x402 cover the parsing and formatting at each step.

Prerequisites

  • A Blockchain0x account and a sk_test_ API key.
  • npm install @blockchain0x/node @blockchain0x/x402.
  • Node 18 or newer. The SDK targets >=18.

The 402 response body

The body a server returns on step 2 has a fixed shape. It is version: 1, a resource string, and a non-empty accepts array of requirements:

JSON
{
  "version": 1,
  "resource": "POST /v1/enrich",
  "accepts": [
    {
      "scheme": "exact-usdc",
      "network": "testnet",
      "chainId": "eip155:84532",
      "payToAddress": "0x...",
      "amountWeiUsdc": "10000",
      "paymentRequestId": "pr_..."
    }
  ]
}

amountWeiUsdc is the price in 6-decimal USDC base units, so 10000 is one cent. On the payer side you do not parse this by hand; parse402Response validates the shape and returns it typed, throwing an X402WireError if the body is missing, malformed, or carries an unrecognized requirement.

The X-Payment header

On step 3 the caller retries with a header in the form scheme:base64(payload):

TEXT
X-Payment: exact-usdc:eyJ...base64...

The scheme is the same exact-usdc the requirement quoted, and the payload is the encoded payment. buildPaymentHeader produces this string and parsePaymentHeader decodes it back to a typed payment, each rejecting an unknown scheme or a malformed payload with an X402WireError so you can branch cleanly. The header travels on the retried request to the same resource, not a separate endpoint.

Implement the payer side

The payer reads the 402, settles, and retries. The settlement, producing a valid payment, is what createX402Client exists to do, so the honest implementation leans on it rather than hand-building the on-chain authorization:

TYPESCRIPT
import { createClient } from "@blockchain0x/node";
import { createX402Client } from "@blockchain0x/x402/client";
import { parse402Response } from "@blockchain0x/x402";

const sdk = createClient({ apiKey: process.env.B0X_API_KEY! });
const fetchWithPay = createX402Client({ sdk });

// The client does the four-step handshake internally. If you want to inspect
// the requirement before paying, parse a raw 402 yourself first:
const probe = await fetch("https://api.example.com/v1/enrich", { method: "POST" });
if (probe.status === 402) {
  const x402 = await parse402Response(probe.clone()); // typed: version, resource, accepts[]
  console.log(x402.accepts[0].amountWeiUsdc, x402.accepts[0].network);
}

// Then let the client handle settle + retry:
const res = await fetchWithPay("https://api.example.com/v1/enrich", { method: "POST" });

Note the probe.clone(): parse402Response consumes the body, so clone the response if you need it again. That single gotcha trips people who parse the same response twice.

Implement the receiver side

The receiver returns the 402 and verifies the retry. You can construct the 402 body yourself, and you decode the header with parsePaymentHeader, but the verification, confirming the payment really settled, goes through paymentRequests.settle rather than trusting the decoded header:

TYPESCRIPT
import { parsePaymentHeader, X402WireError } from "@blockchain0x/x402";

function handle(req, res) {
  const header = req.headers["x-payment"];
  if (!header) {
    res.statusCode = 402;
    return res.end(JSON.stringify(build402Body())); // version 1, resource, accepts[]
  }
  try {
    const payment = parsePaymentHeader(header); // typed; throws X402WireError on bad input
    // verify the payment really settled before serving:
    // await sdk.paymentRequests.settle({ paymentRequestId, body: { txHash, payerAddress, amountUsdcVerified } });
    return serveResult(req, res);
  } catch (err) {
    if (err instanceof X402WireError) {
      res.statusCode = 402;
      return res.end(JSON.stringify({ code: err.code }));
    }
    throw err;
  }
}

The key discipline is the same as any paid integration: never trust the decoded header alone, confirm settlement with the SDK. The helpers parse and format the wire; paymentRequests.settle is what makes the payment real. If you are on Fastify or Express, the adapter does all of this for you and you should use it; the hand-rolled version above is for the stacks it does not cover.

Handle the wire errors

When you implement by hand, the wire helpers do not throw vague errors; they throw an X402WireError with a specific code, and handling each one well is the difference between a debuggable integration and a mysterious one. The codes fall into two groups.

Parsing a 402 on the payer side can raise response.not_402 (the response was not a 402 at all), response.body_missing (no body), or response.body_malformed (the body did not match the version-1 shape). These usually mean the upstream is not speaking x402, or not the version you expect, so treat them as "this endpoint is not a payable x402 resource" rather than retrying.

Parsing the X-Payment header on the receiver side can raise header.missing, header.malformed, header.unknown_scheme (a scheme other than exact-usdc), or header.payload_malformed. These tell you exactly why a caller's payment did not parse, so log the code on every rejection. In the first week of a hand-rolled receiver, that one field is what tells you whether callers are sending nothing, sending garbage, or speaking a scheme you do not support, and each points at a different fix.

Common pitfalls

Three traps.

Draining the 402 body twice. parse402Response reads the body. Clone the response first if you also need it elsewhere, or you will get an empty-body error.

Trusting the parsed header as proof. Decoding the X-Payment header is not verification. A decoded payment still has to be confirmed with paymentRequests.settle. Skipping that is how a hand-rolled receiver leaks revenue.

Reimplementing what the adapter already does. On Fastify or Express, the server adapter and createX402Client are the supported path. Hand-roll only when your stack genuinely has no adapter.

What to ship today

If you are on Fastify or Express, use the adapter and the client; do not hand-roll. If you are not, build the 402 body in the documented shape, parse the retry with parsePaymentHeader, and verify with paymentRequests.settle. Either way, test the handshake on Base Sepolia first. The adapter version is in how-to-add-x402-to-my-api, and the MCP version in how-to-add-payments-to-mcp-server. Pricing is on the pricing page.

FAQ

Frequently asked questions.

Do I need to implement x402 by hand?

Usually no. For paying, createX402Client wraps the whole handshake; for receiving on Fastify or Express, the server adapter does it. Implement by hand only when you are on a stack the adapters do not cover, or when you want to understand the wire format. This page is for those cases; for the common path, use the client and adapter.

What is in the 402 body?

A version (1), a resource string, and an accepts array of payment requirements. Each requirement has scheme exact-usdc, a network, a chainId, a payToAddress, an amountWeiUsdc in 6-decimal base units, and a paymentRequestId. parse402Response validates this shape and returns it typed, throwing X402WireError if it is malformed.

What does the X-Payment header look like?

It is the string scheme:base64(payload), where the scheme is exact-usdc and the payload is the encoded payment. buildPaymentHeader produces it and parsePaymentHeader decodes it back to a typed payment, rejecting an unknown scheme or malformed payload with X402WireError. The header rides on the retried request to the same resource.

Can I build the payment payload myself?

Building a valid payment involves settling on chain, which the SDK and client handle; the wire helpers format and parse the header, they do not produce the on-chain authorization for you. In practice you let createX402Client produce the payment on the payer side and use parsePaymentHeader plus paymentRequests.settle to verify on the receiver side.

Which scheme and network does x402 use here?

The only scheme today is exact-usdc, settling USDC (6 decimals) on Base: mainnet eip155:8453 or testnet eip155:84532. parse402Response and parsePaymentHeader reject any other scheme. You do not negotiate a scheme; you implement the one that ships.

Create your free agent wallet in 5 minutes.

First payment confirmed in under ten minutes. Free to start.