Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.x402r.org/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks you through setting up an Express server that accepts x402r escrow-backed payments. By the end, you’ll have a paid API endpoint protected by the x402 payment middleware with refundable escrow support.

Prerequisites

  • Node.js 20+
  • A deployed PaymentOperator contract (Deploy Operator)
  • A running facilitator service
  • Base Sepolia ETH for testing

Setup

1

Create project and install dependencies

mkdir merchant-server && cd merchant-server
npm init -y
npm install express @x402/core @x402/express @x402r/evm dotenv
2

Configure environment variables

Create a .env file in the project root:
# Replace ADDRESS with your merchant address.
ADDRESS=0x321651df4593DA57C413579c5b611D1A90168a3A
# Replace OPERATOR_ADDRESS with the operator you deployed.
OPERATOR_ADDRESS=0xa0d4734842df1690a5B33Cb21828c946e39D55a2
FACILITATOR_URL=http://localhost:4022
3

Create the server

Create index.ts:
import "dotenv/config";
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { AuthCaptureEvmScheme } from "@x402r/evm/auth-capture/server";
import { getChainConfig } from "@x402r/core";
import { HTTPFacilitatorClient } from "@x402/core/server";

const address = process.env.ADDRESS as `0x${string}`;
const operatorAddress = process.env.OPERATOR_ADDRESS as `0x${string}`;
if (!address || !operatorAddress) {
  console.error("Missing required environment variables: ADDRESS, OPERATOR_ADDRESS");
  process.exit(1);
}

const facilitatorUrl = process.env.FACILITATOR_URL;
if (!facilitatorUrl) {
  console.error("FACILITATOR_URL environment variable is required");
  process.exit(1);
}
const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });

const networkId = "eip155:84532";

const app = express();

const now = Math.floor(Date.now() / 1000);

app.use(
  paymentMiddleware(
    {
      "GET /weather": {
        accepts: [
          {
            scheme: "auth-capture",
            price: "$0.01",
            network: networkId,
            payTo: address,
            maxTimeoutSeconds: 60,
            extra: {
              name: "USDC",
              version: "2",
              captureAuthorizer: operatorAddress,
              captureDeadline: now + 60 * 60,         // capture within 1 hour
              refundDeadline: now + 24 * 60 * 60,     // refund window 24 hours
              feeRecipient: operatorAddress,
              minFeeBps: 0,
              maxFeeBps: 500,
              // assetTransferMethod defaults to "eip3009"
              // autoCapture defaults to false (two-phase)
            },
          },
        ],
        description: "Weather data",
        mimeType: "application/json",
      },
    },
    new x402ResourceServer(facilitatorClient).register(
      networkId,
      new AuthCaptureEvmScheme(),
    ),
  ),
);

app.get("/weather", (req, res) => {
  res.send({
    report: { weather: "sunny", temperature: 70 },
  });
});

app.listen(4021, () => {
  console.log("Server listening at http://localhost:4021");
});
4

Start the server

npx tsx index.ts
You should see:
Server listening at http://localhost:4021
5

Test the endpoint

curl http://localhost:4021/weather
Without a valid payment header, the server responds with HTTP 402 and the auth-capture payment requirements:
{
  "x402Version": 2,
  "accepts": [{
    "scheme": "auth-capture",
    "price": "$0.01",
    "network": "eip155:84532",
    "payTo": "0x...",
    "maxTimeoutSeconds": 60,
    "extra": {
      "name": "USDC",
      "version": "2",
      "captureAuthorizer": "0x...",
      "captureDeadline": 1740758554,
      "refundDeadline": 1741276954,
      "feeRecipient": "0x...",
      "minFeeBps": 0,
      "maxFeeBps": 500
    }
  }]
}

How it works

  • extra config declares the captureAuthorizer, capture/refund deadlines, fee recipient, and fee bounds. The canonical AuthCaptureEscrow and token collector addresses are universal CREATE2 deploys, so routes do not need to repeat them.
  • AuthCaptureEvmScheme registers the auth-capture payment scheme with the x402 resource server so it can verify auth-capture-backed payments.
  • paymentMiddleware intercepts requests, checks for a valid payment header, and returns 402 when the caller has not provided one.
  • HTTPFacilitatorClient connects to the facilitator service that verifies and settles payments on-chain.

Next Steps

forwardToArbiter()

Forward escrow settlements to an arbiter service.

Merchant SDK

Capture payments, handle refunds, and manage escrow.

Deploy Operator

Deploy your own PaymentOperator contract.