Payments

Server Side

Accept x402 payments in your APIs using any x402-compatible client. Your server can verify and settle payments using thirdweb's facilitator service or any custom facilitator.

Payment Flow

The x402 protocol follows this flow:

x402 Protocol Flow
  • Client Request - Client makes a request to your API
  • Payment Required - Server responds with 402 and payment requirements
  • Client Signs - Client signs payment authorization
  • Paid Request - Client retries with payment header
  • Verify & Settle - Server verifies and settles the payment
  • Success - Server returns the protected content

Verify vs Settle

You have two options for handling payments:

Option 1: Settle Payment Directly

Use settlePayment() to verify and settle the payment in one step. This is the simplest approach:

const result = await settlePayment(paymentArgs);
if (result.status === 200) {
// Payment settled, do the paid work
return Response.json({ data: "premium content" });
}

Option 2: Verify First, Then Settle

Use verifyPayment() first, do the work, then settlePayment(). This is useful when:

  • The final price might be dynamic based on the work performed
  • You want to ensure the payment is valid before doing expensive work
  • You need to calculate resource usage before charging
// First verify the payment is valid
const verifyResult = await verifyPayment(paymentArgs);
if (verifyResult.status !== 200) {
return Response.json(verifyResult.responseBody, {
status: verifyResult.status,
headers: verifyResult.responseHeaders,
});
}
// Do the expensive work that requires payment
const result = await doExpensiveWork();
// Now settle the payment based on actual usage
const settleResult = await settlePayment({
...paymentArgs,
price: calculateDynamicPrice(result), // adjust price based on usage
});
return Response.json(result);

Dedicated Endpoint Examples

Protect individual API endpoints with x402 payments:

// app/api/premium-content/route.ts
import { settlePayment, facilitator } from "thirdweb/x402";
import { createThirdwebClient } from "thirdweb";
import { arbitrumSepolia } from "thirdweb/chains";
const client = createThirdwebClient({
secretKey: process.env.THIRDWEB_SECRET_KEY,
});
const thirdwebFacilitator = facilitator({
client,
serverWalletAddress: "0x1234567890123456789012345678901234567890",
});
export async function GET(request: Request) {
const paymentData = request.headers.get("x-payment");
// Verify and process the payment
const result = await settlePayment({
resourceUrl: "https://api.example.com/premium-content",
method: "GET",
paymentData,
payTo: "0x1234567890123456789012345678901234567890",
network: arbitrumSepolia,
price: "$0.10", // or { amount: "100000", asset: { address: "0x...", decimals: 6 } }
facilitator: thirdwebFacilitator,
routeConfig: {
description: "Access to premium API content",
mimeType: "application/json",
maxTimeoutSeconds: 300,
},
});
if (result.status === 200) {
// Payment verified and settled successfully
return Response.json({ data: "premium content" });
} else {
// Payment required
return Response.json(result.responseBody, {
status: result.status,
headers: result.responseHeaders,
});
}
}

Middleware Examples

Protect multiple endpoints with a shared middleware:

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { settlePayment, facilitator } from "thirdweb/x402";
import { createThirdwebClient } from "thirdweb";
import { arbitrumSepolia } from "thirdweb/chains";
const client = createThirdwebClient({
secretKey: process.env.THIRDWEB_SECRET_KEY,
});
const thirdwebFacilitator = facilitator({
client,
serverWalletAddress: "0x1234567890123456789012345678901234567890",
});
export async function middleware(request: NextRequest) {
const method = request.method.toUpperCase();
const resourceUrl = request.nextUrl.toString();
const paymentData = request.headers.get("x-payment");
const result = await settlePayment({
resourceUrl,
method,
paymentData,
payTo: "0x1234567890123456789012345678901234567890",
network: arbitrumSepolia,
price: "$0.01",
routeConfig: {
description: "Access to paid content",
mimeType: "application/json",
maxTimeoutSeconds: 300,
},
facilitator: thirdwebFacilitator,
});
if (result.status === 200) {
// Payment successful, continue to the route
const response = NextResponse.next();
// Set payment receipt headers
for (const [key, value] of Object.entries(
result.responseHeaders,
)) {
response.headers.set(key, value);
}
return response;
}
// Payment required
return NextResponse.json(result.responseBody, {
status: result.status,
headers: result.responseHeaders,
});
}
// Configure which paths the middleware should run on
export const config = {
matcher: ["/api/paid/:path*"],
};

Price Configuration

You can specify prices in multiple ways:

USD String

price: "$0.10"; // 10 cents in USD

ERC20 Token

price: {
amount: "100000", // Amount in base units (100000 = 0.1 USDC with 6 decimals)
asset: {
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // Token address
}
}

Native Token

Payments in native tokens are not currently supported.