This guide walks you through integrating with x402 to enable payments for your API or service. By the end, your API will be able to charge buyers and AI agents for access.
Prerequisites
Before you begin, ensure you have:
- A crypto wallet to receive funds (any EVM-compatible wallet, e.g., CDP Wallet)
- A Coinbase Developer Platform (CDP) account and API Keys
- Node.js and npm installed
- An existing API or server
1. Install Dependencies
2. Add Payment Middleware
Integrate the payment middleware into your application. You will need to provide:
- The Facilitator URL or facilitator object. For testing, use https://x402.org/facilitator which works on Base Sepolia. For mainnet, you can use the facilitator from @coinbase/x402.
- The routes you want to protect.
- Your receiving wallet address.
Full example in the repo here.
import express from "express";
import { paymentMiddleware, Network } from "x402-express";
const app = express();
app.use(paymentMiddleware(
"0xYourAddress", // your receiving wallet address
{ // Route configurations for protected endpoints
"GET /weather": {
// USDC amount in dollars
price: "$0.001",
network: "base-sepolia",
},
},
{
url: "https://x402.org/facilitator", // Facilitator URL for Base Sepolia testnet.
}
));
// Implement your route
app.get("/weather", (req, res) => {
res.send({
report: {
weather: "sunny",
temperature: 70,
},
});
});
app.listen(4021, () => {
console.log(`Server listening at http://localhost:4021`);
});
This is the interface for the payment middleware config:
interface PaymentMiddlewareConfig {
description?: string; // Description of the payment
mimeType?: string; // MIME type of the resource
maxTimeoutSeconds?: number; // Maximum time for payment (default: 60)
outputSchema?: Record; // JSON schema for the response
customPaywallHtml?: string; // Custom HTML for the paywall
resource?: string; // Resource URL (defaults to request URL)
}
When a request is made to this route without payment, your server will respond with the HTTP 402 Payment Required code and payment instructions.
Full example in the repo here.
import express from "express";
import { paymentMiddleware, Network } from "x402-express";
const app = express();
app.use(paymentMiddleware(
"0xYourAddress", // your receiving wallet address
{ // Route configurations for protected endpoints
"GET /weather": {
// USDC amount in dollars
price: "$0.001",
network: "base-sepolia",
},
},
{
url: "https://x402.org/facilitator", // Facilitator URL for Base Sepolia testnet.
}
));
// Implement your route
app.get("/weather", (req, res) => {
res.send({
report: {
weather: "sunny",
temperature: 70,
},
});
});
app.listen(4021, () => {
console.log(`Server listening at http://localhost:4021`);
});
This is the interface for the payment middleware config:
interface PaymentMiddlewareConfig {
description?: string; // Description of the payment
mimeType?: string; // MIME type of the resource
maxTimeoutSeconds?: number; // Maximum time for payment (default: 60)
outputSchema?: Record; // JSON schema for the response
customPaywallHtml?: string; // Custom HTML for the paywall
resource?: string; // Resource URL (defaults to request URL)
}
When a request is made to this route without payment, your server will respond with the HTTP 402 Payment Required code and payment instructions.
Full example in the repo here.
Note: You must set the following environment variables: CDP_API_KEY_ID,
and CDP_API_KEY_SECRET
.
import express from "express";
import { paymentMiddleware } from "x402-express";
import { facilitator } from "@coinbase/x402";
const app = express();
app.use(
paymentMiddleware(
"0xYourAddress", // your receiving wallet address
{ // Route configurations for protected endpoints
"GET /weather": {
// USDC amount in dollars
price: "$0.001",
network: "base",
},
},
// Pass the mainnet facilitator to the payment middleware
facilitator,
),
);
app.get("/weather", (req, res) => {
res.send({
report: {
weather: "sunny",
temperature: 70,
},
});
});
app.listen(4021, () => {
console.log(`Server listening at http://localhost:4021`);
});
This is the interface for the payment middleware config:
interface PaymentMiddlewareConfig {
description?: string; // Description of the payment
mimeType?: string; // MIME type of the resource
maxTimeoutSeconds?: number; // Maximum time for payment (default: 60)
outputSchema?: Record; // JSON schema for the response
customPaywallHtml?: string; // Custom HTML for the paywall
resource?: string; // Resource URL (defaults to request URL)
}
When a request is made to this route without payment, your server will respond with the HTTP 402 Payment Required code and payment instructions.
Full example in the repo here.
import express from "express";
import { paymentMiddleware, Network } from "x402-express";
const app = express();
app.use(paymentMiddleware(
"0xYourAddress", // your receiving wallet address
{ // Route configurations for protected endpoints
"GET /weather": {
// USDC amount in dollars
price: "$0.001",
network: "base-sepolia",
},
},
{
url: "https://x402.org/facilitator", // Facilitator URL for Base Sepolia testnet.
}
));
// Implement your route
app.get("/weather", (req, res) => {
res.send({
report: {
weather: "sunny",
temperature: 70,
},
});
});
app.listen(4021, () => {
console.log(`Server listening at http://localhost:4021`);
});
This is the interface for the payment middleware config:
interface PaymentMiddlewareConfig {
description?: string; // Description of the payment
mimeType?: string; // MIME type of the resource
maxTimeoutSeconds?: number; // Maximum time for payment (default: 60)
outputSchema?: Record; // JSON schema for the response
customPaywallHtml?: string; // Custom HTML for the paywall
resource?: string; // Resource URL (defaults to request URL)
}
When a request is made to this route without payment, your server will respond with the HTTP 402 Payment Required code and payment instructions.
Full example in the repo here.
import express from "express";
import { paymentMiddleware, Network } from "x402-express";
const app = express();
app.use(paymentMiddleware(
"0xYourAddress", // your receiving wallet address
{ // Route configurations for protected endpoints
"GET /weather": {
// USDC amount in dollars
price: "$0.001",
network: "base-sepolia",
},
},
{
url: "https://x402.org/facilitator", // Facilitator URL for Base Sepolia testnet.
}
));
// Implement your route
app.get("/weather", (req, res) => {
res.send({
report: {
weather: "sunny",
temperature: 70,
},
});
});
app.listen(4021, () => {
console.log(`Server listening at http://localhost:4021`);
});
This is the interface for the payment middleware config:
interface PaymentMiddlewareConfig {
description?: string; // Description of the payment
mimeType?: string; // MIME type of the resource
maxTimeoutSeconds?: number; // Maximum time for payment (default: 60)
outputSchema?: Record; // JSON schema for the response
customPaywallHtml?: string; // Custom HTML for the paywall
resource?: string; // Resource URL (defaults to request URL)
}
When a request is made to this route without payment, your server will respond with the HTTP 402 Payment Required code and payment instructions.
Full example in the repo here.
Note: You must set the following environment variables: CDP_API_KEY_ID,
and CDP_API_KEY_SECRET
.
import express from "express";
import { paymentMiddleware } from "x402-express";
import { facilitator } from "@coinbase/x402";
const app = express();
app.use(
paymentMiddleware(
"0xYourAddress", // your receiving wallet address
{ // Route configurations for protected endpoints
"GET /weather": {
// USDC amount in dollars
price: "$0.001",
network: "base",
},
},
// Pass the mainnet facilitator to the payment middleware
facilitator,
),
);
app.get("/weather", (req, res) => {
res.send({
report: {
weather: "sunny",
temperature: 70,
},
});
});
app.listen(4021, () => {
console.log(`Server listening at http://localhost:4021`);
});
This is the interface for the payment middleware config:
interface PaymentMiddlewareConfig {
description?: string; // Description of the payment
mimeType?: string; // MIME type of the resource
maxTimeoutSeconds?: number; // Maximum time for payment (default: 60)
outputSchema?: Record; // JSON schema for the response
customPaywallHtml?: string; // Custom HTML for the paywall
resource?: string; // Resource URL (defaults to request URL)
}
When a request is made to this route without payment, your server will respond with the HTTP 402 Payment Required code and payment instructions.
Full example in the repo here. Since this is a fullstack example, we recommend using the example to build this yourself, and treat the code snippet below as a reference.
import { paymentMiddleware, Network } from 'x402-next';
// Configure the payment middleware
export const middleware = paymentMiddleware(
"0xYourAddress", // your receiving wallet address
{ // Route configurations for protected endpoints
'/protected': {
price: '$0.01',
network: "base-sepolia",
config: {
description: 'Access to protected content'
}
},
}
{
url: "https://x402.org/facilitator", // Facilitator URL for Base Sepolia testnet.
}
);
// Configure which paths the middleware should run on
export const config = {
matcher: [
'/protected/:path*',
]
};
This is the interface for the payment middleware config:
interface PaymentMiddlewareConfig {
description?: string; // Description of the payment
mimeType?: string; // MIME type of the resource
maxTimeoutSeconds?: number; // Maximum time for payment (default: 60)
outputSchema?: Record; // JSON schema for the response
customPaywallHtml?: string; // Custom HTML for the paywall
resource?: string; // Resource URL (defaults to request URL)
}
When a request is made to this route without payment, your server will respond with the HTTP 402 Payment Required code and payment instructions.
Full example in the repo here. Since this is a fullstack example, we recommend using the example to build this yourself, and treat the code snippet below as a reference.
import { paymentMiddleware, Network } from 'x402-next';
// Configure the payment middleware
export const middleware = paymentMiddleware(
"0xYourAddress", // your receiving wallet address
{ // Route configurations for protected endpoints
'/protected': {
price: '$0.01',
network: "base-sepolia",
config: {
description: 'Access to protected content'
}
},
}
{
url: "https://x402.org/facilitator", // Facilitator URL for Base Sepolia testnet.
}
);
// Configure which paths the middleware should run on
export const config = {
matcher: [
'/protected/:path*',
]
};
This is the interface for the payment middleware config:
interface PaymentMiddlewareConfig {
description?: string; // Description of the payment
mimeType?: string; // MIME type of the resource
maxTimeoutSeconds?: number; // Maximum time for payment (default: 60)
outputSchema?: Record; // JSON schema for the response
customPaywallHtml?: string; // Custom HTML for the paywall
resource?: string; // Resource URL (defaults to request URL)
}
When a request is made to this route without payment, your server will respond with the HTTP 402 Payment Required code and payment instructions.
Full example in the repo here. Since this is a fullstack example, we recommend using the example to build this yourself, and treat the code snippet below as a reference.
Note: You must set the following environment variables: CDP_API_KEY_ID
, and CDP_API_KEY_SECRET
.
import { paymentMiddleware } from 'x402-next';
import { facilitator } from "@coinbase/x402";
// Configure the payment middleware
export const middleware = paymentMiddleware(
"0xYourAddress", // your receiving wallet address
{ // Route configurations for protected endpoints
'/protected': {
price: '$0.01',
network: "base",
config: {
description: 'Access to protected content'
}
},
},
// Pass the mainnet facilitator to the payment middleware
facilitator,
);
// Configure which paths the middleware should run on
export const config = {
matcher: [
'/protected/:path*',
]
};
This is the interface for the payment middleware config:
interface PaymentMiddlewareConfig {
description?: string; // Description of the payment
mimeType?: string; // MIME type of the resource
maxTimeoutSeconds?: number; // Maximum time for payment (default: 60)
outputSchema?: Record; // JSON schema for the response
customPaywallHtml?: string; // Custom HTML for the paywall
resource?: string; // Resource URL (defaults to request URL)
}
When a request is made to this route without payment, your server will respond with the HTTP 402 Payment Required code and payment instructions.
Full example in the repo here.
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { paymentMiddleware, Network } from "x402-hono";
const app = new Hono();
// Configure the payment middleware
app.use(paymentMiddleware(
"0xYourAddress", // your receiving wallet address
{ // Route configurations for protected endpoints
"/protected-route": {
price: "$0.10",
network: "base-sepolia",
config: {
description: "Access to premium content",
}
}
},
{
url: "https://x402.org/facilitator", // Facilitator URL for Base Sepolia testnet.
}
));
// Implement your route
app.get("/protected-route", (c) => {
return c.json({ message: "This content is behind a paywall" });
});
serve({
fetch: app.fetch,
port: 3000
});
This is the interface for the payment middleware config:
interface PaymentMiddlewareConfig {
description?: string; // Description of the payment
mimeType?: string; // MIME type of the resource
maxTimeoutSeconds?: number; // Maximum time for payment (default: 60)
outputSchema?: Record; // JSON schema for the response
customPaywallHtml?: string; // Custom HTML for the paywall
resource?: string; // Resource URL (defaults to request URL)
}
When a request is made to this route without payment, your server will respond with the HTTP 402 Payment Required code and payment instructions.
Full example in the repo here.
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { paymentMiddleware, Network } from "x402-hono";
const app = new Hono();
// Configure the payment middleware
app.use(paymentMiddleware(
"0xYourAddress", // your receiving wallet address
{ // Route configurations for protected endpoints
"/protected-route": {
price: "$0.10",
network: "base-sepolia",
config: {
description: "Access to premium content",
}
}
},
{
url: "https://x402.org/facilitator", // Facilitator URL for Base Sepolia testnet.
}
));
// Implement your route
app.get("/protected-route", (c) => {
return c.json({ message: "This content is behind a paywall" });
});
serve({
fetch: app.fetch,
port: 3000
});
This is the interface for the payment middleware config:
interface PaymentMiddlewareConfig {
description?: string; // Description of the payment
mimeType?: string; // MIME type of the resource
maxTimeoutSeconds?: number; // Maximum time for payment (default: 60)
outputSchema?: Record; // JSON schema for the response
customPaywallHtml?: string; // Custom HTML for the paywall
resource?: string; // Resource URL (defaults to request URL)
}
When a request is made to this route without payment, your server will respond with the HTTP 402 Payment Required code and payment instructions.
Full example in the repo here.
Note: You must set the following environment variables: CDP_API_KEY_ID
, and CDP_API_KEY_SECRET
.
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { paymentMiddleware } from "x402-hono";
import { facilitator } from "@coinbase/x402";
const app = new Hono();
// Configure the payment middleware
app.use(paymentMiddleware(
"0xYourAddress", // your receiving wallet address
{ // Route configurations for protected endpoints
"/protected-route": {
price: "$0.10",
network: "base",
config: {
description: "Access to premium content",
}
}
},
// Pass the mainnet facilitator to the payment middleware
facilitator,
));
// Implement your route
app.get("/protected-route", (c) => {
return c.json({ message: "This content is behind a paywall" });
});
serve({
fetch: app.fetch,
port: 3000
});
This is the interface for the payment middleware config:
interface PaymentMiddlewareConfig {
description?: string; // Description of the payment
mimeType?: string; // MIME type of the resource
maxTimeoutSeconds?: number; // Maximum time for payment (default: 60)
outputSchema?: Record; // JSON schema for the response
customPaywallHtml?: string; // Custom HTML for the paywall
resource?: string; // Resource URL (defaults to request URL)
}
When a request is made to this route without payment, your server will respond with the HTTP 402 Payment Required code and payment instructions.
3. Test Your Integration
To verify:
- Make a request to your endpoint (e.g.,
curl http://localhost:3000/your-endpoint
).
- The server responds with a 402 Payment Required, including payment instructions in the body.
- Complete the payment using a compatible client, wallet, or automated agent. This typically involves signing a payment payload, which is handled by the client SDK detailed in the Quickstart for Buyers.
- Retry the request, this time including the
X-PAYMENT
header containing the cryptographic proof of payment (payment payload).
- The server verifies the payment via the facilitator and, if valid, returns your actual API response (e.g.,
{ "data": "Your paid API response." }
).
4. Error Handling
- If you get an error stating
Cannot find module 'x402-hono/express' or its corresponding type declarations.
, add the tsconfig.json from the Hono example to your project.
npm install
the dependencies in each example
Next Steps
For questions or support, join our Discord.
Summary
This quickstart covered:
- Installing the x402 SDK and relevant middleware
- Adding payment middleware to your API and configuring it
- Testing your integration
Your API is now ready to accept crypto payments through x402.