DEV Community

Nikhil Ranka
Nikhil Ranka

Posted on

x402 Explained: HTTP-Native Micropayments for AI Agents (With Real Code)

x402 Explained: HTTP‑Native Micropayments for AI Agents (With Real Code)

Target audience: developers building autonomous AI agents that need to charge or pay for micro‑services without reinventing a billing layer.


Why Micropayments Matter for Agents

Autonomous agents frequently compose capabilities: they call a language model, hit a data‑fetcher, run a sentiment analyzer, etc. Each of those calls is cheap—often a fraction of a cent—but traditional invoicing, API‑key rotation, or subscription models add friction and overhead.

The x402 specification repurposes the HTTP 402 Payment Required status code to let a server signal that a request can be fulfilled only after the client presents a verifiable, on‑chain payment. Because the mechanism lives entirely in the request/response flow, agents can treat paid endpoints just like any other REST resource: they send a GET/POST, receive a 402, attach a payment proof, and retry—no SDK, no account creation, no long‑lived secrets.

Below we walk through the protocol, show a minimal server‑side implementation, demonstrate a client that an AI agent could embed, and discuss the honest trade‑offs you’ll encounter when adopting x402 in production.


The x402 Flow in a Nutshell

Step Actor Action
1 Client Sends a normal HTTP request (e.g., GET /summary).
2 Server If no valid payment is present, returns 402 Payment Required with a WWW-Authenticate: x402 header that contains:
network (e.g., base)
token (e.g., USDC)
amount (in wei)
payload (a hash of the request details)
maxFeePerGas / maxPriorityFeePerGas (optional)
3 Client Constructs an EIP‑2718 typed transaction that pays the server’s x402.payTo address the exact amount, signs it with the agent’s wallet, and encodes the signed transaction (or a relayer‑signed meta‑transaction) as the value of an X-Payment header.
4 Client Retries the original request, adding the X-Payment header.
5 Server Verifies the signature, checks that the nonce matches the payload, confirms sufficient funds were transferred, then processes the request and returns 2xx with the result.

Because the payment proof is just a signed transaction, the server can verify it off‑chain (e.g., by calling eth_sendRawTransaction to a local node or using a trusted relayer) before executing the business logic, ensuring that no work is done for free.


Minimal Server Implementation (Node.js + Express)

## Server: x402 middleware

The following snippet shows a self‑contained Express middleware that enforces x402 for a route. It assumes:

* The agent’s wallet address is known (`payTo`).
* The token is USDC on Base (contract `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`).
* Amounts are expressed in the token’s smallest unit (6 decimals for USDC).
Enter fullscreen mode Exit fullscreen mode
// x402-middleware.js
const crypto = require('crypto');
const ethers = require('ethers');

// CONFIGURATION -------------------------------------------------
const NETWORK = 'base';               // Chain identifier used in the header
const TOKEN   = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base
const PAY_TO  = '0xYourAgentWallet'; // Where payments go
// ----------------------------------------------------------------

/**
 * Generates the WWW-Authenticate challenge for a 402 response.
 * `payload` is a hash of the request method + path + query string.
 */
function challenge(req) {
  const payload = crypto
    .createHash('sha256')
    .update(`${req.method}${req.path}${JSON.stringify(req.query)}`)
    .digest('hex');

  return `x402 network="${NETWORK}", token="${TOKEN}", amount="1000000", payload="${payload}"`;
  // 1,000,000 USDC = 0.001 USDC (6 decimals) → $0.001 ≈ $0.01 after fees
}

/**
 * Verifies that the X-Payment header contains a signed transaction
 * that pays exactly the challenged amount to PAY_TO.
 */
async function verifyPayment(req, res, next) {
  const auth = req.get('WWW-Authenticate') || '';
  const match = auth.match(/amount="(\d+)"/);
  if (!match) return res.status(400).send('Missing amount in challenge');

  const expectedAmount = BigInt(match[1]); // wei‑style integer for the token
  const xpay = req.get('X-Payment');
  if (!xpay) return res.status(402).set('WWW-Authenticate', challenge(req)).send('Payment Required');

  try {
    const tx = ethers.Transaction.from(xpay);
    // Basic checks
    if (tx.to.toLowerCase() !== PAY_TO.toLowerCase())
      throw new Error('Wrong payee');
    if (tx.value !== expectedAmount)
      throw new Error('Incorrect amount');
    if (tx.chainId !== ethers.getNetwork(NETWORK).chainId)
      throw new Error('Wrong network');

    // Optionally, simulate the transaction to ensure it would succeed
    const provider = new ethers.JsonRpcProvider('https://base-mainnet.g.alchemy.com/v2/<YOUR_KEY>');
    await provider.call(tx); // throws if revert

    // Attach the verified tx to request for downstream use (e.g., refunds)
    req.x402Tx = tx;
    next();
  } catch (e) {
    console.warn('x402 verification failed:', e.message);
    return res.status(402).set('WWW-Authenticate', challenge(req)).send('Invalid payment proof');
  }
}

// Example protected route -------------------------------------------------
const express = require('express');
const app = express();

app.get('/summary', verifyPayment, (req, res) => {
  // Here you would call your actual AI service (LLM, data fetch, etc.)
  const fakeResult = { summary: 'This is a dummy summary of the requested content.' };
  res.json(fakeResult);
});

app.listen(3000, () => console.log('x402 demo listening on :3000'));
Enter fullscreen mode Exit fullscreen mode

What this code does

  1. Challenge generation – When a request lacks an X-Payment header, the middleware returns 402 with a WWW-Authenticate header that encodes the amount (here 0.001 USDC) and a payload derived from the request itself. Binding the payload prevents replay attacks across different endpoints.
  2. Verification – The client must resend the same request with an X-Payment header containing a signed Ethereum transaction (EIP‑1559) that transfers exactly the challenged amount to PAY_TO. The middleware checks the signature, chain ID, nonce (implicit via the transaction), and that the transaction would not revert.
  3. Processing – Only after a valid proof does the route handler run.

Note: In production you’d likely offload transaction submission to a relayer or use ERC‑4337 account abstraction so the agent doesn’t need to hold native Base gas; the example keeps things simple to illustrate the core idea.


Agent‑Side Client (JavaScript/TypeScript)

An autonomous agent can embed the following helper to automatically handle 402 challenges. It uses ethers.js for signing and node-fetch (or the browser’s fetch) for HTTP.


ts
// x402-client.ts
import { ethers } from 'ethers';
import fetch from 'node-fetch';

const PRIVATE_KEY = '0xyour_agent_private_key'; // NEVER hard‑code in prod; use a vault or env
const wallet = new ethers.Wallet(PRIVATE_KEY);
const provider = new ethers.JsonRpcProvider('https://base-mainnet.g.alchemy.com/v2/<YOUR_KEY>');
const signer = wallet.connect(provider);

/**
 * Performs a request that may require x402 payment.
 * Retries automatically if a 402 is received with a valid challenge.
 */
async function x402Fetch(url: string, init: RequestInit = {}): Promise<Response> {
  let attempt = 0;
  const maxAttempts = 2; // first try, then one retry after payment

  while (attempt < maxAttempts) {
    attempt++;
    const res = await fetch(url, init);

    if (res.status !== 402) return res; // success or other error

    // Parse the challenge
    const wwwAuth = res.headers.get('WWW-Authenticate') || '';
    const match = wwwAuth.match(/amount="(\d+)"/);
    if (!match) throw new Error('Malformed 402 challenge');

    const amountWei = BigInt(match[1]); // token's smallest unit
    const payloadMatch = wwwAuth.match(/payload="([^"]+)"/);
    if (!payloadMatch) throw new Error('Missing payload in challenge');
    const payload = payloadMatch[1];

    // Build a transaction that pays the challenged amount to the server
    // In a real implementation you would extract the payee from the challenge
    // (x402 allows the server to include a
Enter fullscreen mode Exit fullscreen mode

Top comments (0)