How do I add x402 payments to an Express / Next.js API?
Quick answer: on Express, wrap routes with paymentMiddleware from @x402/express. On Next.js, use paymentProxy for page routes (or many routes at once) and withX402 for API route handlers — withX402 settles only after a successful response (status < 400). Start on Base Sepolia with the x402.org facilitator; for mainnet switch network to eip155:8453 and a production facilitator. Full official walkthrough: Quickstart for Sellers.
1. Install
Express (versions we used on 2026-10-15):
npm install express @x402/express@2.28.0 @x402/core@2.28.0 @x402/evm@2.28.0
# add @x402/svm / @x402/avm only if you accept Solana or Algorand
Next.js: install @x402/next instead of @x402/express, plus the same core and scheme packages. We did not install and run a pinned @x402/next locally in this test; treat the Next.js sections below as docs-sourced until you smoke-test them yourself.
2. Minimal Express example (Base Sepolia)
Adapted from the official Express snippet (EVM-only for clarity). Recipient is a placeholder — put your wallet there.
// server.mjs — npm i express @x402/express@2.28.0 @x402/core@2.28.0 @x402/evm@2.28.0
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
const app = express();
const evmAddress = "0xYourEvmAddress"; // your receive wallet
const facilitatorClient = new HTTPFacilitatorClient({
url: "https://x402.org/facilitator", // testnet only
});
app.use(
paymentMiddleware(
{
"GET /weather": {
accepts: [{
scheme: "exact",
price: "$0.001",
network: "eip155:84532", // Base Sepolia
payTo: evmAddress,
}],
description: "Weather data",
mimeType: "application/json",
},
},
new x402ResourceServer(facilitatorClient)
.register("eip155:*", new ExactEvmScheme()), // covers Sepolia + mainnet
),
);
app.get("/weather", (req, res) => {
res.send({ report: { weather: "sunny", temperature: 70 } });
});
app.listen(4021);
Without a payment, the middleware answers HTTP 402 with payment instructions in the PAYMENT-REQUIRED header. The client signs, retries with PAYMENT-SIGNATURE, and the facilitator verifies and settles.
3. What we observed locally (2026-10-15)
We ran the Express snippet above with packages @x402/express / @x402/core / @x402/evm 2.28.0 and a burn-address payTo. Unpaid curl:
# Local smoke (packages pinned 2.28.0)
node server.mjs
curl -i http://localhost:4021/weather
# Observed 2026-10-15: HTTP/1.1 402 Payment Required
# Header PAYMENT-REQUIRED present (base64 JSON, x402Version 2,
# accepts exact on eip155:84532, amount 1000 atomic USDC).
# Body: {}
Decoded PAYMENT-REQUIRED payload included x402Version: 2, resource URL http://localhost:4021/weather, and one accepts entry: scheme exact, network eip155:84532, amount 1000 (atomic USDC for $0.001), asset Base Sepolia USDC 0x036CbD…CF7e. We did not complete a paid retry in this run (no signed testnet payment), so settlement behavior is unknown from our test — follow the buyer quickstart for that step.
4. Next.js: paymentProxy vs withX402
The docs give two approaches. Key difference (quoted from the quickstart): withX402 only settles payment after a successful response (status < 400), making it the recommended approach for API routes.
| Helper | Best for | Settlement timing |
|---|---|---|
paymentProxy | Page routes, or many routes with one config | Can charge even when an API handler returns an error — prefer withX402 for APIs |
withX402 | Individual API route handlers | Settles only after status < 400 |
Both snippets below are adapted from the official Next.js examples (EVM-only for clarity; the docs also show Solana).
Option A — paymentProxy (page / multi-route):
// proxy.ts — npm i @x402/next @x402/core@2.28.0 @x402/evm@2.28.0
import { paymentProxy } from "@x402/next";
import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server";
import { ExactEvmScheme } from "@x402/evm/exact/server";
export const evmAddress = "0xYourEvmAddress";
const facilitatorClient = new HTTPFacilitatorClient({
url: "https://x402.org/facilitator",
});
export const server = new x402ResourceServer(facilitatorClient);
server.register("eip155:*", new ExactEvmScheme());
export const proxy = paymentProxy(
{
"/protected": {
accepts: [{
scheme: "exact",
price: "$0.001",
network: "eip155:84532",
payTo: evmAddress,
}],
description: "Premium content",
mimeType: "text/html",
},
},
server,
);
export const config = { matcher: ["/protected/:path*"] };
Option B — withX402 (API route; recommended for handlers):
// app/api/weather/route.ts
import { NextRequest, NextResponse } from "next/server";
import { withX402 } from "@x402/next";
import { server, evmAddress } from "../../../proxy";
const handler = async (_: NextRequest) => {
return NextResponse.json(
{ report: { weather: "sunny", temperature: 72 } },
{ status: 200 },
);
};
export const GET = withX402(
handler,
{
"/api/weather": { // must match the served path exactly (incl. basePath)
accepts: [{
scheme: "exact",
price: "$0.001",
network: "eip155:84532",
payTo: evmAddress,
}],
description: "Access to weather API",
mimeType: "application/json",
},
},
server,
);
Path-key rule: the second argument to withX402 is a RoutesConfig. Key it by the route’s path pattern (e.g. "/api/weather") so discovery indexes the correct path. The pattern must match the request path exactly as served, including any basePath. If it does not match, the handler runs without payment protection and a warning is logged once — per the official docs. We did not re-trigger that warning locally; treat the wording as docs-sourced.
5. Mainnet: do not reuse the testnet facilitator
The quickstart’s Running on Mainnet section is explicit: do not reuse https://x402.org/facilitator for mainnet routes — it is for testnet development only. On @x402/core 2.28.0 you need three edits. If the server still has .register("eip155:84532", …) (as the official Express quickstart shows), changing only the facilitator URL and network makes paymentMiddleware log RouteConfigurationError: … No scheme implementation registered for "exact" on network "eip155:8453" at startup, and the process exits:
- Facilitator URL → a production facilitator (examples in the docs: Coinbase CDP
https://api.cdp.coinbase.com/platform/v2/x402, PayAIhttps://facilitator.payai.network). Compare fees in which facilitator to use and what it costs or the free facilitator cost calculator; more detail in which facilitators work on Base mainnet. - Network →
eip155:8453(Base mainnet) instead ofeip155:84532. - Register →
.register("eip155:8453", new ExactEvmScheme())or.register("eip155:*", new ExactEvmScheme())(wildcard matches every EVM chain id — same pattern as the official Next.js samples). The Express and Next.js snippets in this guide already useeip155:*, so if you copied them you only need steps 1 and 2.
// Mainnet deltas on the Express server from §2
const facilitatorClient = new HTTPFacilitatorClient({
url: "https://facilitator.payai.network", // or CDP — not x402.org/facilitator
});
// …
network: "eip155:8453", // was eip155:84532
// …
new x402ResourceServer(facilitatorClient)
.register("eip155:*", new ExactEvmScheme()), // was eip155:84532 only
Official facilitator list: docs.x402.org/dev-tools/facilitators.
6. exact vs upto (one-liner)
exact charges the advertised price. upto lets the client authorize a maximum while you settle the actual usage with setSettlementOverrides (price = max authorized). Full scheme details and examples are in the same quickstart; this guide does not duplicate them.
7. Test before mainnet
- Run the Express smoke test above on Sepolia; get faucet USDC with testing x402 on Base Sepolia.
- Paste your paid URL into the free x402 endpoint checker — it grades status,
PAYMENT-REQUIRED, network, asset, amount units, and more. - Charging per MCP tool call instead of HTTP? See how to charge for an MCP tool with x402.
Sponsor slot open: one short text line here for 30 days. See terms.
FAQ
Which package do I install for Express vs Next.js?
Express: npm install @x402/express @x402/core @x402/evm (add @x402/svm / @x402/avm if you need those networks). Next.js: @x402/next plus the same core/scheme packages. Versions checked 2026-10-15: @x402/express 2.28.0.
paymentProxy or withX402 for Next.js API routes?
Use withX402 for API routes: it settles only after a successful response (status < 400). paymentProxy is better for page routes or protecting many routes at once; on API routes it can charge even when the handler returns an error.
Why does withX402 skip payment protection?
The RoutesConfig path key must match the request path exactly as served, including any basePath. If it does not match, the handler runs unprotected and a warning is logged once — per the official quickstart.
Can I use https://x402.org/facilitator on mainnet?
No. The docs say that facilitator is for testnet development only. On Base mainnet: (1) a production facilitator URL (CDP or PayAI), (2) network eip155:8453, and (3) register eip155:8453 or eip155:* — registering only eip155:84532 makes paymentMiddleware log RouteConfigurationError (No scheme implementation registered for "exact" on network "eip155:8453") at startup and exit on @x402/express 2.28.0. See our mainnet and facilitator-cost guides.
Sources, checked 2026-10-15: docs.x402.org Quickstart for Sellers (Express + Next.js + Running on Mainnet + schemes), Quickstart for Buyers, npm @x402/express 2.28.0, npm @x402/next, Facilitators. Local smoke test: express 5.3.0 + @x402/express 2.28.0 → HTTP 402 + PAYMENT-REQUIRED. Next.js helpers not run locally in this test.
Prefer a ready-made Worker storefront? The x402 Seller Kit sells a digital file for USDC on Base without a facilitator. The README is free to read.
Get the kit · $29