x402 Pay-Per-Request API Payments
Add HTTP-native micropayments to any API with x402. Servers return 402 Payment Required, clients pay with USDC on StarkNet, and requests are fulfilled automatically.
Acknowledgments
Thanks to Annabel Lim from the Starknet Foundation for proposing x402 and always supporting the Chipi team.
What is x402?
x402 is an HTTP-native payment protocol that lets you monetize any API endpoint. Instead of API keys, subscriptions, or billing dashboards, payments happen inline with every HTTP request.
How it works:
- Client sends a request to a protected endpoint
- Server returns
402 Payment Requiredwith price, asset, and recipient - Client signs a USDC payment on StarkNet and retries with an
X-PAYMENTheader - Server verifies the payment via a facilitator and fulfills the request
No signups. No invoices. No billing disputes. Just HTTP requests with built-in payments.
Client-Side: React / Next.js
The useX402Payment hook handles the full 402 flow automatically. It's a drop-in replacement for fetch().
bashnpm install @chipi-stack/nextjs@latesttsximport { useX402Payment } from "@chipi-stack/nextjs";
function PremiumContent() {
const { payFetch, totalSpent, isPaying } = useX402Payment({
wallet: userWallet,
encryptKey: passkeyCredential,
bearerToken: process.env.NEXT_PUBLIC_CHIPI_API_KEY!,
maxAmount: "1.00", // Safety cap per request in USDC
});
const loadData = async () => {
// If the server returns 402, the hook pays and retries automatically
const response = await payFetch("https://api.example.com/premium-data");
const data = await response.json();
};
return (
<button onClick={loadData} disabled={isPaying}>
{isPaying ? "Processing payment..." : "Load Premium Data"}
</button>
);
}- -
payFetchworks exactly likefetch()but handles 402 responses - -
maxAmountprevents unexpectedly large charges - -
isPayingtracks payment state for UI feedback - -
totalSpentshows cumulative USDC spent in the session
Server-Side: Express Middleware
Add x402 payment gating to any Express endpoint with a single middleware call.
bashnpm install @chipi-stack/x402typescriptimport express from "express";
import { x402Middleware } from "@chipi-stack/x402";
const app = express();
// Protect an endpoint with x402
app.use("/api/premium", x402Middleware({
amount: "0.10", // USDC per request
recipient: process.env.MERCHANT_WALLET!, // Your StarkNet wallet
facilitatorUrl: "https://x402.chipipay.com",
network: "starknet-mainnet",
asset: "USDC",
}));
app.get("/api/premium/data", (req, res) => {
// Only reached after payment is verified and settled
res.json({ data: "premium content" });
});The middleware handles everything: returning the 402 response with payment details, verifying the X-PAYMENT header, and settling USDC on-chain via the facilitator.
Server-Side: Next.js API Route
For Next.js App Router, use the middleware in your route handler.
typescriptimport { x402Middleware } from "@chipi-stack/x402";
const paywall = x402Middleware({
amount: "0.05",
recipient: process.env.MERCHANT_WALLET!,
facilitatorUrl: "https://x402.chipipay.com",
network: "starknet-mainnet",
asset: "USDC",
});
export async function GET(req: Request) {
const paymentResult = await paywall(req);
if (paymentResult) return paymentResult; // Returns 402 if unpaid
return Response.json({ data: "premium content" });
}Server-Side: FastAPI (Python)
For Python backends, use the chipi_x402 package.
bashpip install chipi-x402pythonimport os
from fastapi import FastAPI
from chipi_x402 import x402_middleware
app = FastAPI()
app.add_middleware(
x402_middleware,
amount="0.10",
recipient=os.environ["MERCHANT_WALLET"],
facilitator_url="https://x402.chipipay.com",
network="starknet-mainnet",
asset="USDC",
)
@app.get("/api/premium")
async def premium():
return {"data": "premium content"}Manual Verification with X402Facilitator
For custom server logic, use the X402Facilitator class directly.
typescriptimport { X402Facilitator } from "@chipi-stack/x402";
const facilitator = new X402Facilitator({
rpcUrl: "https://starknet-mainnet.infura.io/v3/YOUR_KEY",
usdcAddress: "0x033068f6539f8e6e6b131e6b2b814e6c34a5224bc66947c47dab9dfee93b35fb",
});
// In your route handler:
const paymentHeader = request.headers.get("X-PAYMENT");
if (!paymentHeader) {
return Response.json({
price: "0.10",
asset: "USDC",
network: "starknet-mainnet",
recipient: process.env.MERCHANT_WALLET,
facilitatorUrl: "https://x402.chipipay.com",
}, { status: 402 });
}
const result = await facilitator.verify({
paymentHeader,
expectedAmount: "0.10",
expectedRecipient: process.env.MERCHANT_WALLET!,
});
if (result.valid) {
const txHash = await facilitator.settle(result.payment);
// Fulfill the request
}SNIP-12 Typed Data Signing
For advanced use cases, build and sign x402 payment data manually using SNIP-12 (StarkNet's EIP-712 equivalent).
typescriptimport { buildX402TypedData } from "@chipi-stack/core";
import { signTypedData } from "@chipi-stack/core";
// Build the payment typed data
const typedData = buildX402TypedData({
recipient: merchantAddress,
amount: "0.10", // Human-readable USDC
nonce: crypto.randomUUID(),
expiry: 300, // Seconds until payment expires
});
// Sign with the wallet's private key
const signature = await signTypedData({
encryptedPrivateKey: wallet.encryptedPrivateKey,
encryptKey: passkeyCredential,
typedData,
});The signature is then base64-encoded and sent as the X-PAYMENT header.
Session Keys for Automated Payments
Combine x402 with session keys to eliminate the passkey prompt on every request. After a one-time session registration, all subsequent x402 payments are signed automatically.
tsximport { useX402Payment } from "@chipi-stack/nextjs";
import { useChipiSession } from "@chipi-stack/nextjs";
function AutomatedPremiumAPI() {
const { hasActiveSession, registerSession } = useChipiSession();
const { payFetch } = useX402Payment({
wallet: userWallet,
encryptKey: passkeyCredential,
bearerToken: process.env.NEXT_PUBLIC_CHIPI_API_KEY!,
});
const loadStream = async () => {
// After session registration, payFetch signs payments
// with the session key — no passkey prompt per request
const response = await payFetch("https://api.example.com/stream");
const data = await response.json();
};
return <button onClick={loadStream}>Load Stream</button>;
}This is ideal for streaming APIs, real-time data feeds, or any endpoint that's called frequently.
Environment Variables
Client-side (.env.local):
bashNEXT_PUBLIC_CHIPI_API_KEY=pk_prod_YOUR_KEYServer-side (.env):
bashMERCHANT_WALLET=0xYOUR_STARKNET_WALLET_ADDRESS- - Payments are in USDC on StarkNet mainnet (6 decimals)
- - The facilitator at
https://x402.chipipay.comhandles verification and settlement - - All transactions are gasless for the payer
- - Each payment has a unique nonce for replay protection