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)

When building autonomous AI agents, traditional SaaS billing models present a significant engineering bottleneck. Traditional API access relies on pre-funded accounts, monthly subscriptions, or credit cards tied to API keys. For an autonomous agent traversing the web to gather data, execute tasks, and coordinate with other agents, this centralized paradigm is highly inefficient.

An agent cannot easily input a credit card, manage 50 different SaaS subscriptions, or handle the security risks of storing highly-privileged long-lived API keys.

The architectural solution is native to the web itself: HTTP Status Code 402 (Payment Required). While reserved for decades, the rise of low-latency Layer 2 blockchains and stablecoins makes programmatic, pay-as-you-go HTTP-native payments viable.

Below, we’ll explore the design pattern of x402—a standardized implementation of HTTP 402 for machine-to-machine (M2M) micropayments—and look at the production-ready code to implement both sides of the handshake.


The x402 Handshake Protocol

The x402 protocol shifts the payment responsibility from a pre-established off-chain relationship to an on-demand, request-response handshake.

┌──────────┐                 GET /endpoint                 ┌──────────┐
│          │──────────────────────────────────────────────>│          │
│          │  <── 402 Payment Required ──────────────────  │          │
│          │      X-402-Payment-Destination: 0x...        │          │
│    AI    │      X-402-Amount-USDC: 10000 (0.01 USDC)     │   API    │
│  Agent   │      X-402-Chain-Id: 8453 (Base)              │ Provider │
│          │                                               │          │
│ (Client) │  ─── GET /endpoint ────────────────────────>  │ (Server) │
│          │      X-402-Payment-Proof: 0xTxHash...         │          │
│          │  <── 200 OK (With payload) ─────────────────  │          │
└──────────┘                                               └──────────┘
Enter fullscreen mode Exit fullscreen mode
  1. Initial Request: The agent makes an unauthenticated HTTP request to an endpoint.
  2. Payment Challenge (402): The server responds with an HTTP 402 Payment Required status. It embeds payment metadata in the headers: target wallet address, price (e.g., in USDC), and the target network (e.g., Base).
  3. Settlement: The agent’s execution loop catches the 402, constructs an on-chain transaction matching the criteria, signs it with its operational wallet, and submits it to the network.
  4. Resubmission with Proof: The agent retries the original request, attaching the transaction hash in the X-402-Payment-Proof header.
  5. Execution (200): The server verifies the transaction against the blockchain ledger and serves the requested resource.

The Implementation

Let's write a production-grade implementation using TypeScript, Hono (for the server-side), and viem (for the client-side agent execution). We will use USDC on the Base network (Chain ID: 8453) due to sub-cent gas fees and instant settlement.

1. Server-Side: Enforcing and Verifying x402

This middleware intercepts requests, checks for a valid payment proof, verifies it against an RPC provider, and returns a 402 if unpaid.


typescript
import { Hono } from 'hono';
import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';

const app = new Hono();

// Base USDC contract address
const USDC_BASE_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bda02913';
const RECIPIENT_WALLET = '0xYourMerchantWalletAddressHere...';
const ENDPOINT_COST_USDC = 10000n; // 0.01 USDC (USDC has 6 decimals)

const publicClient = createPublicClient({
  chain: base,
  transport: http('https://mainnet.base.org')
});

// Transfer event signature for ERC-20: Transfer(address indexed from, address indexed to, uint256 value)
const TRANSFER_EVENT_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';

async function verifyPayment(txHash: `0x${string}`): Promise<boolean> {
  try {
    const receipt = await publicClient.getTransactionReceipt({ hash: txHash });

    // Mitigate double-spending: Check block age to prevent replay of old transactions
    const currentBlock = await publicClient.getBlockNumber();
    if (currentBlock - receipt.blockNumber > 100n) {
      return false; // Transaction is too old
    }

    // Parse logs for USDC transfer to our merchant address
    for (const log of receipt.logs) {
      if (log.address.toLowerCase() === USDC_BASE_ADDRESS.toLowerCase()) {
        const isTransfer = log.topics[0] === TRANSFER_EVENT_TOPIC;
        const toAddressMatched = log.topics[2] && 
          ('0x' + log.topics[2].slice(26)).toLowerCase() === RECIPIENT_WALLET.toLowerCase();

        // Decode transfer value
        if (isTransfer && toAddressMatched && log.data) {
          const value = Big
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
cailab profile image
CAI

The on-chain settlement part of x402 makes sense for machine-to-machine payments, but there is a prior step most write-ups skip. How does a non-human entity even get a wallet in the first place, and who sets the spending rules?

An agent waking up in a cloud VM doesn't have a credit card or bank account. It can't go through KYC. So the wallet has to come from somewhere else - its operator, a hosted wallet, or some delegated credential system. And once the agent has a signing key, what stops it from spending more than intended?

The more interesting architecture splits the flow: the agent proposes a payment, a separate authorizer (credential vault, rules engine, or human) confirms it, then the transaction goes through. That way the agent negotiates pricing autonomously without holding unrestricted spending power. This proposal-then-confirm pattern is something we're working on at CAI, though the space is still early enough that multiple approaches will probably coexist.