x402

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

Exact vs Upto Payment Schemes

The thirdweb x402 client/server stack supports two payment schemes: exact and upto.

  • exact - The client pays the exact amount specified in the payment requirements.
  • upto - The client pays any amount up to the specified maximum amount.

By default, the payment scheme is exact. You can specify the payment scheme in the settlePayment() or verifyPayment() arguments.

Exact Payment Scheme

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

const result = await settlePayment({
resourceUrl: "https://api.example.com/premium-content",
method: "GET",
paymentData,
payTo: "0x1234567890123456789012345678901234567890",
network: arbitrum,
price: "$0.10",
facilitator: thirdwebFacilitator,
});
if (result.status === 200) {
// Payment settled, do the paid work
return Response.json({ data: "premium content" });
}

Upto Payment Scheme

For dynamic pricing, use verifyPayment() first, do the work, then settlePayment():

  • The final price can be dynamic based on the work performed
  • Ensures the payment is valid before doing the expensive work

This is great for AI apis that need to charge based on the token usage for example. Check out a fully working example check out this x402 ai inference example.

Here's a high level example of how to use the upto payment scheme with a dynamic price based on the token usage. First we verify the payment is valid for the max payable amount and then settle the payment based on the actual usage.

const paymentArgs = {
resourceUrl: "https://api.example.com/premium-content",
method: "GET",
paymentData,
payTo: "0x1234567890123456789012345678901234567890",
network: arbitrum,
scheme: "upto", // enables dynamic pricing
price: "$0.10", // max payable amount
facilitator: thirdwebFacilitator,
};
// First verify the payment is valid for the max amount
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 { answer, tokensUsed } = await callExpensiveAIModel();
// Now settle the payment based on actual usage
const pricePerTokenUsed = 0.00001; // ex: $0.00001 per AI model token used
const settleResult = await settlePayment({
...paymentArgs,
price: tokensUsed * pricePerTokenUsed, // adjust final price based on usage
});
return Response.json(answer);

Price and Token Configuration

You can specify prices in multiple ways:

USD String

This will default to using USDC on the specified network.

network: polygon, // or any other EVM chain
price: "$0.10" // 10 cents in USDC

ERC20 Token

You can use any ERC20 token that supports the ERC-2612 permit or ERC-3009 sign with authorization.

Simply pass the amount in base units and the token address.

network: arbitrum,
price: {
amount: "1000000000000000", // Amount in base units (0.001 tokens with 18 decimals)
asset: {
address: "0xf01E52B0BAC3E147f6CAf956a64586865A0aA928", // Token address
}
}

Native Token

Payments in native tokens are not currently supported.

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",
},
});
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*"],
};