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:

  1. Client sends a request to a protected endpoint
  2. Server returns 402 Payment Required with price, asset, and recipient
  3. Client signs a USDC payment on StarkNet and retries with an X-PAYMENT header
  4. 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@latest
tsximport { 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>
  );
}
  • - payFetch works exactly like fetch() but handles 402 responses
  • - maxAmount prevents unexpectedly large charges
  • - isPaying tracks payment state for UI feedback
  • - totalSpent shows 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/x402
typescriptimport 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-x402
pythonimport 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_KEY

Server-side (.env):

bashMERCHANT_WALLET=0xYOUR_STARKNET_WALLET_ADDRESS
  • - Payments are in USDC on StarkNet mainnet (6 decimals)
  • - The facilitator at https://x402.chipipay.com handles verification and settlement
  • - All transactions are gasless for the payer
  • - Each payment has a unique nonce for replay protection

Ready to build?

Connect the MCP server and start building in minutes.

Get Started