Skip to main content

Accept Payments

Protect your HTTP endpoints with x402 payments using middleware. The middleware enforces the x402 handshake, forwards payment requirements to a facilitator, inspects payment headers from clients, and handles settlement before allowing requests to proceed.

Prerequisites

  • Node.js and npm installed
  • A SKALE Chain endpoint
  • Understanding of x402 protocol
  • A facilitator service (see Run a Facilitator)

Overview

When a client reaches a paywalled endpoint, the middleware:
  1. Returns a 402 Payment Required response with payment requirements if the incoming request lacks a valid payment header
  2. When a payment header is present, forwards it to the facilitator’s /verify and /settle endpoints
  3. If settlement succeeds, the request continues; otherwise another 402 is returned

Environment Setup

Create a .env file with the following variables:
# Facilitator URL
FACILITATOR_URL=https://facilitator.dirtroad.dev

# Your wallet address to receive payments
FACILITATOR_RECEIVING_ADDRESS=0xfacilitator_receiving_address

# USDC token contract on SKALE
PAYMENT_TOKEN_ADDRESS=0x2e08028E3C4c2356572E096d8EF835cD5C6030bD
PAYMENT_TOKEN_NAME="Bridged USDC (SKALE Bridge)"

# SKALE Chain ID
NETWORK_CHAIN_ID=324705682

# Server port
PORT=3000

Implementation

Step 1: Install Dependencies

npm install hono @hono/node-server @x402/hono @x402/evm @x402/core dotenv

Step 2: Create the Server

import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { paymentMiddleware, x402ResourceServer } from "@x402/hono";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
import type { Network } from "@x402/core/types";
import "dotenv/config";

const app = new Hono();

async function main() {
    const facilitatorUrl = process.env.FACILITATOR_URL!;
    const receivingAddress = process.env.RECEIVING_ADDRESS as `0x${string}`;
    const paymentTokenAddress = process.env.PAYMENT_TOKEN_ADDRESS as `0x${string}`;
    const paymentTokenName = process.env.PAYMENT_TOKEN_NAME || "Bridged USDC (SKALE Bridge)";
    const networkChainId = process.env.NETWORK_CHAIN_ID || "324705682";
    const network: Network = `eip155:${networkChainId}`;

    // Setup facilitator client and resource server
    const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });
    const resourceServer = new x402ResourceServer(facilitatorClient);
    
    // Register the exact scheme for EVM networks
    resourceServer.register("eip155:*", new ExactEvmScheme());

    // Public endpoint
    app.get("/", (c) => {
        return c.json({ message: "Welcome! Access /premium/* endpoints for paid content" });
    });

    // Apply payment middleware to protected routes
    app.use(
        paymentMiddleware(
            {
                "GET /premium/data": {
                    accepts: [
                        {
                            scheme: "exact",
                            network: network,
                            payTo: receivingAddress,
                            price: {
                                amount: "10000",
                                asset: paymentTokenAddress,
                                extra: {
                                    name: paymentTokenName,
                                    version: "1",
                                },
                            },
                        },
                    ],
                    description: "Premium data access",
                    mimeType: "application/json",
                },
            },
            resourceServer,
        ),
    );

    // Protected endpoint
    app.get("/premium/data", (c) => {
        return c.json({
            secret: "Premium data unlocked!",
            timestamp: new Date().toISOString(),
        });
    });

    const port = Number(process.env.PORT) || 3000;
    serve({ fetch: app.fetch, port }, () => {
        console.log(`Server running on http://localhost:${port}`);
    });
}

main().catch(console.error);

Step 3: Run the Server

npx tsx server.ts

Multiple Protected Routes

Add multiple routes with different pricing:
app.use(
    paymentMiddleware(
        {
            "GET /premium/data": {
                accepts: [
                    {
                        scheme: "exact",
                        network: network,
                        payTo: receivingAddress,
                        price: {
                            amount: "10000",
                            asset: paymentTokenAddress,
                            extra: { name: paymentTokenName, version: "1" },
                        },
                    },
                ],
                description: "Basic premium data",
                mimeType: "application/json",
            },
            "GET /premium/advanced": {
                accepts: [
                    {
                        scheme: "exact",
                        network: network,
                        payTo: receivingAddress,
                        price: {
                            amount: "50000",
                            asset: paymentTokenAddress,
                            extra: { name: paymentTokenName, version: "1" },
                        },
                    },
                ],
                description: "Advanced premium data",
                mimeType: "application/json",
            },
        },
        resourceServer,
    ),
);

Testing Your Server

Once your server is running, you can test it:
# Public endpoint (no payment required)
curl http://localhost:3000/

# Protected endpoint (returns 402 Payment Required)
curl http://localhost:3000/premium/data
The protected endpoint will return a 402 Payment Required response with payment requirements that a client can use to make a payment.

Error Handling

  • Invalid facilitator responses throw an exception (HTTP 500)
  • When settlement fails, the middleware returns a 402 status with error details
  • Missing configuration returns a 503 Service Unavailable

Next Steps

Resources