Skip to main content

Overview

The x402 server components let you monetize your API endpoints. Clients pay with USDC per request, and payments are settled gaslessly through Chipi’s paymaster. Two main components:
  • X402Facilitator — Verifies payment signatures and settles via paymaster
  • x402Middleware — Express middleware that handles the full 402 flow (also compatible with Express-like frameworks such as Hono)

Express / Node.js

Quick Setup

import express from "express";
import { ChipiServerSDK, x402Middleware } from "@chipi-stack/backend";

const sdk = new ChipiServerSDK({
  apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
  apiSecretKey: process.env.CHIPI_SECRET_KEY!,
});

const facilitator = sdk.createX402Facilitator();
const app = express();

// Protect an endpoint — requires 0.01 USDC per request
app.get("/premium",
  x402Middleware({
    facilitator,
    paymentConfig: {
      amount: "0.01",
      payTo: "0xYOUR_MERCHANT_ADDRESS",
      description: "Premium API access",
    },
  }),
  (req, res) => {
    // Payment was verified and settled
    res.json({
      data: "premium content",
      txHash: req.x402.txHash,
    });
  }
);

app.listen(3000);

Middleware Behavior

  1. No X-PAYMENT header → Returns 402 with PAYMENT-REQUIRED header
  2. Malformed header → Returns 400
  3. Invalid payment → Returns 402 with error reason
  4. Valid payment → Settles via paymaster, attaches req.x402, calls next()

Access Payment Info

After successful payment, req.x402 contains:
{
  txHash: "0x...",        // Starknet transaction hash
  payment: { ... },       // Full PaymentPayload
  networkId: "starknet-mainnet"
}

Verify-Only Mode

To verify payments without auto-settling (for custom settlement logic):
app.get("/custom",
  x402Middleware({
    facilitator,
    paymentConfig: { amount: "0.01", payTo: "0xADDRESS" },
    verifyOnly: true,  // Don't auto-settle
  }),
  async (req, res) => {
    // Payment is verified but not settled
    // Do your own settlement logic here
    const result = await facilitator.settle(req.x402.payment);
    res.json({ txHash: result.transactionHash });
  }
);

Custom Nonce Store

By default, the facilitator uses an in-memory nonce store (resets on server restart). For production, plug in a persistent store:

Redis Example

import Redis from "ioredis";

const redis = new Redis();

const facilitator = sdk.createX402Facilitator(undefined, {
  nonceStore: {
    has: async (nonce) => (await redis.exists(`x402:nonce:${nonce}`)) === 1,
    consume: async (nonce) => {
      // SET NX with 24h expiry — atomic check-and-consume
      const result = await redis.set(`x402:nonce:${nonce}`, "1", "EX", 86400, "NX");
      return result === "OK"; // true if newly set (not replayed)
    },
  },
});

PostgreSQL Example

const facilitator = sdk.createX402Facilitator(undefined, {
  nonceStore: {
    has: async (nonce) => {
      const result = await db.query(
        "SELECT 1 FROM x402_nonces WHERE nonce = $1",
        [nonce]
      );
      return result.rows.length > 0;
    },
    consume: async (nonce) => {
      // INSERT … ON CONFLICT DO NOTHING returns rowCount 1 if inserted, 0 if duplicate.
      // This is atomic at the database level.
      const result = await db.query(
        "INSERT INTO x402_nonces (nonce, created_at) VALUES ($1, NOW()) ON CONFLICT DO NOTHING",
        [nonce]
      );
      return (result.rowCount ?? 0) > 0;
    },
  },
});
The x402_nonces table must enforce uniqueness on the nonce column:
CREATE TABLE x402_nonces (
  nonce TEXT PRIMARY KEY,
  created_at TIMESTAMPTZ DEFAULT NOW()
);
For cleanup, run periodically: DELETE FROM x402_nonces WHERE created_at < NOW() - INTERVAL '24 hours'

Python — FastAPI

from fastapi import FastAPI, Depends, Request
from chipi_sdk import ChipiSDK, X402Facilitator, X402PaymentConfig
from chipi_sdk.x402_middleware import fastapi_x402_dependency

sdk = ChipiSDK(config)
facilitator = X402Facilitator(sdk, bearer_token="...")
payment_config = X402PaymentConfig(amount="0.01", pay_to="0xADDRESS")

app = FastAPI()

@app.get("/premium")
async def premium(
    request: Request,
    x402=Depends(fastapi_x402_dependency(facilitator, payment_config))
):
    return {
        "data": "premium content",
        "tx_hash": x402.get("tx_hash"),
    }

Python — Flask

from flask import Flask, jsonify
from chipi_sdk import ChipiSDK, X402Facilitator, X402PaymentConfig
from chipi_sdk.x402_middleware import flask_x402_required

sdk = ChipiSDK(config)
facilitator = X402Facilitator(sdk, bearer_token="...")
payment_config = X402PaymentConfig(amount="0.01", pay_to="0xADDRESS")

app = Flask(__name__)

@app.route("/premium")
@flask_x402_required(facilitator, payment_config)
def premium():
    return jsonify({"data": "premium content"})

Direct Facilitator Usage

For custom server frameworks or advanced use cases:
const facilitator = sdk.createX402Facilitator();

// Verify a payment (no settlement)
const verification = await facilitator.verify(paymentPayload);
if (!verification.isValid) {
  console.error("Invalid:", verification.invalidReason);
}

// Settle a payment (execute USDC transfer)
const settlement = await facilitator.settle(paymentPayload);
if (settlement.success) {
  console.log("Settled:", settlement.transactionHash);
}

CORS Configuration

The middleware automatically skips OPTIONS (preflight) requests. Make sure your CORS setup includes the X-PAYMENT header:
import cors from "cors";

app.use(cors({
  exposedHeaders: ["Payment-Required"],
  allowedHeaders: ["X-PAYMENT", "Content-Type", "Authorization"],
}));