Start accepting x402 payments in your Next.js application in 2 minutes.
You can find the full code for this example on GitHub.
Step 1: Install Dependencies
Install the required packages for your Next.js application:
npm install x402 next react react-domOr with other package managers:
# pnpm
pnpm add x402 next react react-dom
# yarn
yarn add x402 next react react-dom
# bun
bun add x402 next react react-domStep 2: Set Your Environment Variables
Create a .env.local file in your project root:
echo "ADDRESS=0x...\nFACILITATOR_URL=https://x402.bex.co\nNETWORK=sui\nBEX_API_KEY=your_api_key_here" > .env.localYour .env.local file should look like this:
ADDRESS=0x... # wallet public address you want to receive payments to
FACILITATOR_URL=https://x402.bex.co
NETWORK=sui # recommended for fastest settlement
BEX_API_KEY=your_api_key_here # get from https://bex.co/dash/bex.co supports multiple networks:
sui(recommended for fastest settlement)ethereumbasepolygonavalanche
Step 3: Create Payment-Protected API Routes
Next.js uses a file-based routing system. Create API routes in the app/api directory (App Router) or pages/api directory (Pages Router).
Using App Router (Next.js 13+)
Create app/api/weather/route.ts:
import { NextRequest } from "next/server";
import { requirePayment } from "x402-nextjs";
const facilitatorUrl = process.env.FACILITATOR_URL!;
const payTo = process.env.ADDRESS! as `0x${string}`;
const network = process.env.NETWORK || "sui";
const apiKey = process.env.BEX_API_KEY!;
export const GET = requirePayment(
{
price: "$0.001",
network,
},
payTo,
{
url: facilitatorUrl,
apiKey,
},
async (request: NextRequest) => {
return Response.json({
report: {
weather: "sunny",
temperature: 70,
location: "San Francisco",
},
});
},
);Create app/api/premium/content/route.ts:
import { NextRequest } from "next/server";
import { requirePayment } from "x402-nextjs";
const facilitatorUrl = process.env.FACILITATOR_URL!;
const payTo = process.env.ADDRESS! as `0x${string}`;
const network = process.env.NETWORK || "sui";
const apiKey = process.env.BEX_API_KEY!;
export const GET = requirePayment(
{
price: "$0.05",
network,
},
payTo,
{
url: facilitatorUrl,
apiKey,
},
async (request: NextRequest) => {
return Response.json({
content: "This is premium content",
type: "article",
timestamp: new Date().toISOString(),
});
},
);Using Pages Router (Next.js 12 and below)
Create pages/api/weather.ts:
import type { NextApiRequest, NextApiResponse } from "next";
import { requirePayment } from "x402-nextjs/pages";
const facilitatorUrl = process.env.FACILITATOR_URL!;
const payTo = process.env.ADDRESS! as `0x${string}`;
const network = process.env.NETWORK || "sui";
const apiKey = process.env.BEX_API_KEY!;
async function handler(req: NextApiRequest, res: NextApiResponse) {
return res.status(200).json({
report: {
weather: "sunny",
temperature: 70,
location: "San Francisco",
},
});
}
export default requirePayment(
{
price: "$0.001",
network,
},
payTo,
{
url: facilitatorUrl,
apiKey,
},
handler,
);Step 4: Run the Development Server
Start your Next.js development server:
npm run devYour API routes will be available at:
http://localhost:3000/api/weatherhttp://localhost:3000/api/premium/content
Step 5: Test the Server
You can test payments against your API routes using HTTP clients like curl, Postman, or by building a client application.
Client implementation guides for fetch API and axios will be available soon.
Manual Testing with curl
# This will return a 402 Payment Required response
curl http://localhost:3000/api/weather
# The response will include payment details in headersPayment Configuration Options
The requirePayment wrapper accepts flexible payment configurations:
Simple Dollar Amount
export const GET = requirePayment(
{
price: "$0.001",
network: "sui",
},
payTo,
{
url: facilitatorUrl,
apiKey,
},
async (request: NextRequest) => {
return Response.json({ data: "Your data" });
},
);Token Amount with Specific Asset
export const GET = requirePayment(
{
price: {
amount: "10000",
asset: {
address: "0x...", // Token contract address
decimals: 6,
name: "USDC",
},
},
network: "sui",
},
payTo,
{
url: facilitatorUrl,
apiKey,
},
async (request: NextRequest) => {
return Response.json({ data: "Premium data" });
},
);Accessing Payment Information
export const GET = requirePayment(
{
price: "$0.001",
network: "sui",
},
payTo,
{
url: facilitatorUrl,
apiKey,
},
async (request: NextRequest) => {
// Payment information is attached to the request
const paymentData = (request as any).paymentData;
return Response.json({
data: "Your data",
payment: {
txHash: paymentData?.txHash,
network: paymentData?.network,
},
});
},
);Advanced Features
Custom Payment Verification Callback
Add custom logic when payments are verified:
import { requirePayment } from "x402-nextjs";
export const GET = requirePayment(
{
price: "$0.001",
network: "sui",
},
payTo,
{
url: facilitatorUrl,
apiKey,
onPaymentVerified: (paymentData) => {
console.log("Payment verified:", paymentData.txHash);
// Custom logging, analytics, or business logic
},
},
async (request: NextRequest) => {
return Response.json({ data: "Your data" });
},
);Error Handling
Create a custom error handler in app/api/error/route.ts:
import { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
return Response.json(
{
error: "Internal server error",
timestamp: new Date().toISOString(),
},
{ status: 500 },
);
}CORS Configuration
Enable CORS for your API routes using Next.js middleware.
Create middleware.ts in your project root:
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Set CORS headers
response.headers.set("Access-Control-Allow-Origin", "*");
response.headers.set(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS",
);
response.headers.set(
"Access-Control-Allow-Headers",
"Content-Type, Authorization",
);
// Handle preflight requests
if (request.method === "OPTIONS") {
return new NextResponse(null, { status: 200, headers: response.headers });
}
return response;
}
export const config = {
matcher: "/api/:path*",
};This wildcard-origin example is for cross-origin requests without
credentials: "include". For credentialed requests, check the requesting origin
against your application's allowlist, return that exact origin, and add
Access-Control-Allow-Credentials: true to both OPTIONS and final responses.
Include every non-safelisted header your installed payment client sends in
Access-Control-Allow-Headers; name Authorization explicitly. To read custom
payment-response headers in JavaScript, also list those names in
Access-Control-Expose-Headers.
To investigate a failure, open the CORS debugger. Enter the page origin, endpoint, method, credentials mode, and only the headers your client code sets. Copy the OPTIONS response status and headers from the browser's Network panel into the preflight field, then copy the final response headers separately. Leave unavailable evidence blank and inspect one response at a time when redirects occur. The tool explains preflight permission and response-header visibility separately. It never contacts the endpoint; generated curl commands observe HTTP responses but do not enforce browser CORS. The decisions follow the Fetch Standard.
Environment-Specific Configuration
Create different configurations for development and production:
// lib/x402-config.ts
export const x402Config = {
facilitator: {
url: process.env.FACILITATOR_URL || "https://x402.bex.co",
apiKey: process.env.BEX_API_KEY!,
},
network: process.env.NETWORK || "sui",
payTo: process.env.ADDRESS! as `0x${string}`,
};
// Validate configuration
if (!x402Config.facilitator.apiKey || !x402Config.payTo) {
throw new Error("Missing required x402 environment variables");
}Then use it in your routes:
import { requirePayment } from "x402-nextjs";
import { x402Config } from "@/lib/x402-config";
export const GET = requirePayment(
{
price: "$0.001",
network: x402Config.network,
},
x402Config.payTo,
x402Config.facilitator,
async (request: NextRequest) => {
return Response.json({ data: "Your data" });
},
);Production Deployment
Before deploying to production:
- Switch to production network (e.g.,
suiinstead ofsui-testnet) - Use environment variables for all sensitive data
- Enable HTTPS (handled by most hosting platforms)
- Set up proper error logging
- Configure CORS for your production domains
- Add rate limiting to prevent abuse
Deployment on Vercel
Next.js works seamlessly with Vercel. Set your environment variables in the Vercel dashboard:
- Go to your project settings
- Navigate to "Environment Variables"
- Add your variables:
ADDRESSFACILITATOR_URLNETWORKBEX_API_KEY
Deployment on Other Platforms
For other platforms (Netlify, AWS, etc.), ensure your environment variables are set and your build command is:
npm run buildProduction Example
// app/api/data/route.ts
import { NextRequest } from "next/server";
import { requirePayment } from "x402-nextjs";
// Production configuration
const config = {
facilitator: {
url: "https://x402.bex.co",
apiKey: process.env.BEX_API_KEY!,
onPaymentVerified: (paymentData) => {
// Log to your analytics service
console.log("Payment verified:", paymentData.txHash);
},
},
network: "sui", // Production Sui network
payTo: process.env.ADDRESS! as `0x${string}`,
};
export const GET = requirePayment(
{
price: "$0.01",
network: config.network,
},
config.payTo,
config.facilitator,
async (request: NextRequest) => {
try {
// Your business logic here
const data = { content: "Protected data" };
return Response.json({
data,
payment: {
verified: true,
},
});
} catch (error) {
console.error("Error processing request:", error);
return Response.json({ error: "Internal server error" }, { status: 500 });
}
},
);Troubleshooting
Common Issues
Payment verification fails
- Verify your
BEX_API_KEYis correct in.env.local - Check that the wallet address format matches the network
- Ensure the facilitator URL is accessible
- Restart the Next.js dev server after changing environment variables
CORS errors
- Add the middleware configuration as shown above
- Check that the client origin is allowed
- Verify CORS headers are being set correctly
Network mismatch
- Ensure client and server use the same network
- Check that the token address is valid for the network
Environment variables not loading
- Use
.env.localfor local development - Restart the dev server after changes
- Use
NEXT_PUBLIC_prefix only for client-side variables - Never expose sensitive keys (API keys) to the client
TypeScript errors
- Ensure you have
@types/nodeand@types/reactinstalled - Check that your
tsconfig.jsonis properly configured - Use type assertions for environment variables:
process.env.VAR!
Why Choose Next.js?
Next.js is an excellent choice for x402 payment applications:
- Full-stack framework: API routes + React frontend in one project
- Server-side rendering: Optimal performance and SEO
- File-based routing: Intuitive API route organization
- Edge functions: Deploy globally with low latency
- Vercel integration: Zero-config deployments
- TypeScript support: First-class TypeScript experience
App Router vs Pages Router
App Router (Recommended - Next.js 13+)
Pros:
- Modern React features (Server Components, Suspense)
- Better performance and smaller bundle sizes
- Improved routing and layouts
- Built-in loading and error states
Use when:
- Starting a new project
- Want the latest Next.js features
- Need advanced routing capabilities
Pages Router (Legacy)
Pros:
- More mature and stable
- Extensive community resources
- Simpler mental model
Use when:
- Maintaining an existing project
- Need maximum stability
- Team is familiar with older Next.js versions
Next Steps
Need Help?
Join Our Community
Have questions or want to connect with other developers?