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-honoExpress 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:
- No payment headers? Returns
402 Payment Requiredwith a JSON body describing what to pay (amount, network, asset, recipient) - Has payment headers? Forwards the signed authorization to the facilitator's
POST /verifyendpoint - Verification passes? Lets the request through to your route handler and sets
req.payerto the payer's wallet address - After response? Calls
POST /settleon the facilitator, which executestransferWithAuthorizationon-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:
- Reads payment requirements from the response
- Signs an EIP-3009
transferWithAuthorizationwith the client's key - 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.aiNo 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
- Facilitator API — understand what
/verifyand/settledo under the hood - Supported Chains — all supported networks and contract addresses
- Architecture — full system diagram and payment flow