OpenX402

Building an x402 Server

How to build an API server that charges for access using x402 payments

This guide walks through building a server that charges USDC for API access using the x402 protocol. You'll use paymentMiddleware to protect routes and point it at the OpenX402 facilitator to handle verification and on-chain settlement.

Install Dependencies

npm install express x402-express
# or
npm install hono x402-hono

Express Server

import express from "express";
import { paymentMiddleware } from "x402-express";

const app = express();
app.use(express.json());

// Define which routes require payment
const middleware = paymentMiddleware(
  "0xYourWalletAddress" as `0x${string}`,   // where USDC gets sent
  {
    "/api/generate": {
      price: "$0.10",
      network: "base",
      config: { description: "AI text generation" },
    },
    "/api/analyze": {
      price: "$0.50",
      network: "base",
      config: { description: "Data analysis endpoint" },
    },
  },
  {
    url: "https://facilitator.openx402.ai",   // facilitator URL
  }
);

app.use(middleware);

app.get("/api/generate", (req, res) => {
  // req.payer contains the wallet address that paid
  res.json({ result: "generated text", payer: req.payer });
});

app.get("/api/analyze", (req, res) => {
  res.json({ result: "analysis output", payer: req.payer });
});

app.listen(3000);

Hono Server

import { Hono } from "hono";
import { paymentMiddleware } from "x402-hono";

const app = new Hono();

app.use(
  "/api/*",
  paymentMiddleware(
    "0xYourWalletAddress" as `0x${string}`,
    {
      "/api/generate": {
        price: "$0.10",
        network: "base",
        config: { description: "AI text generation" },
      },
    },
    {
      url: "https://facilitator.openx402.ai",
    }
  )
);

app.get("/api/generate", (c) => {
  return c.json({ result: "generated text" });
});

export default app;

How the Middleware Works

When a request hits a protected route, the middleware does this:

  1. No payment headers? Returns 402 Payment Required with a JSON body describing what to pay (amount, network, asset, recipient)
  2. Has payment headers? Forwards the signed authorization to the facilitator's POST /verify endpoint
  3. Verification passes? Lets the request through to your route handler and sets req.payer to the payer's wallet address
  4. After response? Calls POST /settle on the facilitator, which executes transferWithAuthorization on-chain to move USDC from the payer to your wallet

Your server never touches private keys or sends transactions. The facilitator handles all on-chain settlement.

Payment Configuration

Each protected route needs three things:

{
  "/api/route": {
    price: "$0.10",           // dollar amount — converted to USDC atomic units
    network: "base",          // which chain (base, base-sepolia)
    config: {
      description: "What the client is paying for",
    },
  },
}

The payTo address (first argument to paymentMiddleware) is where USDC lands after settlement.

Dynamic Pricing

You can create middleware per-request for dynamic pricing:

app.get("/api/pay/:address/:amount", (req, res, next) => {
  const { address, amount } = req.params;

  const dynamicMiddleware = paymentMiddleware(
    address as `0x${string}`,
    {
      [`/api/pay/${address}/${amount}`]: {
        price: `$${amount}`,
        network: "base",
        config: { description: `Pay $${amount} USDC to ${address}` },
      },
    },
    {
      url: "https://facilitator.openx402.ai",
    }
  );

  dynamicMiddleware(req, res, () => {
    res.json({ success: true, recipient: address, amount });
  });
});

Client Side

Clients use x402-fetch to automatically handle the 402 flow:

import { wrapFetch } from "x402-fetch";

const x402Fetch = wrapFetch(fetch, {
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
});

// Automatically signs and pays when server returns 402
const response = await x402Fetch("https://your-server.com/api/generate");
const data = await response.json();

When x402Fetch gets a 402 response, it:

  1. Reads payment requirements from the response
  2. Signs an EIP-3009 transferWithAuthorization with the client's key
  3. Retries the request with payment headers (PAYMENT-SIGNATURE, PAYMENT-RESPONSE)

The client never sends a transaction — it only signs an authorization that the facilitator later executes.

The Facilitator

All examples on this page use the OpenX402 facilitator at:

https://facilitator.openx402.ai

No sign up, no API keys. It supports Base, Monad, Solana mainnet, and Base Sepolia testnet. See Supported Chains for contract addresses and network IDs.

Use network: "base-sepolia" during development. Get testnet USDC from the Circle faucet.

What Happens On-Chain

When a payment settles, the facilitator calls transferWithAuthorization on the USDC contract. This is an ERC-3009 function that moves tokens using a signed authorization instead of an approve + transferFrom flow.

The transaction moves USDC directly from the payer to your payTo address. The facilitator never custodies funds — it just submits the pre-signed authorization.

Each authorization has a unique nonce. The facilitator tracks nonces to prevent replay attacks (the same payment being submitted twice).

Next Steps