Quickstart for merchants
Gate an HTTP endpoint behind x402 payment in any of Express, Hono, Fastify, or Next.js.
This guide walks through gating an HTTP endpoint behind a paid call. By the end your endpoint will return 402 Payment Required to unpaid agents, accept a signed payment payload on retry, and deliver content only after the on-chain settlement succeeds.
A complete runnable version with both an Express server and an integration test is in examples/express-paid-api/.
What you need
- A facilitator URL the middleware can call. For local development, run the reference facilitator on
:3000. For production, point atfacilitator.x402.nerochain.io. - A merchant address that receives the settlement transfers. This is just an EOA or contract address you control on NERO Chain.
- The package:
pnpm add @nerochain/x402-server.
Express
import express from "express";
import { x402Express } from "@nerochain/x402-server/express";
const app = express();
app.use(express.json());
app.use(
"/api/llm",
x402Express({
paymentRequirements: {
scheme: "aa-native",
network: "eip155:689",
amount: "1000", // 0.001 USDT (atomic units, 6 decimals)
asset: "0x9a2eabdecda3eae051bca75fbe71a3dbdb35f9d4", // DEMO-USDT on NERO testnet
payTo: "0xYourMerchantAddress",
maxTimeoutSeconds: 60,
},
facilitator: { url: "http://localhost:3000" },
resource: { url: "/api/llm", description: "demo paid endpoint" },
}),
(req, res) => {
res.json({ ok: true, payer: req.x402Payer, content: "demo response" });
},
);
app.listen(4040);The middleware does three things automatically:
- On a request without a
PAYMENT-SIGNATUREheader, it returns 402 with thepaymentRequirementspayload. - On a request with a payload, it forwards to the facilitator's
/verify. If verification fails, it returns 402 with the error code. - After your handler runs successfully, it forwards to
/settleand attaches thePAYMENT-RESPONSEheader with the on-chain receipt.
The handler runs only after verification has passed. Inside the handler, req.x402Payer is the SCW that paid.
Hono
import { Hono } from "hono";
import { x402Hono } from "@nerochain/x402-server/hono";
const app = new Hono();
app.use(
"/api/llm",
x402Hono({ paymentRequirements, facilitator: { url } }),
);
app.post("/api/llm", (c) => c.json({ ok: true, payer: c.get("x402Payer") }));Fastify
import Fastify from "fastify";
import { x402Fastify } from "@nerochain/x402-server/fastify";
const app = Fastify();
app.register(x402Fastify, {
prefix: "/api/llm",
paymentRequirements,
facilitator: { url },
});
app.post("/api/llm", async (req) => ({ ok: true, payer: req.x402Payer }));Next.js App Router
// app/api/llm/route.ts
import { x402Next } from "@nerochain/x402-server/next";
export const POST = x402Next(
{ paymentRequirements, facilitator: { url } },
async (req) => Response.json({ ok: true }),
);Pricing and asset selection
paymentRequirements.amount is in atomic units (no decimal point). For a token with 6 decimals like USDT/USDC, 1000 is 0.001 of a token. The asset address must be on the allowlist of the configured SettlementContract. On NERO mainnet that's USDT (0xff13a7a12fd485bc9687ff88d8ae1a6b655ab469), USDC.e (0x97eec1c29f745dc7c267f90292aa663d997a601d), and USDC.arb (0x8712796136Ac8e0EEeC123251ef93702f265aa80); on testnet it's DEMO-USDT (0x9a2eabdecda3eae051bca75fbe71a3dbdb35f9d4).
To accept multiple schemes or assets, pass an array:
paymentRequirements: [
{ scheme: "aa-native", network: "eip155:689", amount: "1000", asset: USDT, payTo, maxTimeoutSeconds: 60 },
{ scheme: "exact", network: "eip155:689", amount: "1000", asset: USDT, payTo, maxTimeoutSeconds: 60 },
],The client picks whichever it can satisfy.
Next
- Facilitator concept — what
/verifyand/settledo. - Networks & tokens — supported assets and addresses.
- Wire format reference — the envelope shape.