> ## Documentation Index
> Fetch the complete documentation index at: https://docs.semanticpay.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Server

This guide shows how to accept x402 payments for your API endpoints using Semantic's facilitator on Plasma or Stable. By the end you'll have an Express server that gates routes behind USD₮ payments.

<Info>
  See a full working demo at [github.com/SemanticPay/x402-usdt0-demo](https://github.com/SemanticPay/x402-usdt0-demo)
</Info>

## Install

```bash theme={null}
npm install @x402/express @x402/evm @x402/core express dotenv
```

## How it works

Your server doesn't handle payments directly. It delegates to Semantic's facilitator:

1. A buyer hits your endpoint without a payment header
2. Your middleware responds with `402 Payment Required` and the payment terms
3. The buyer's x402 client signs an EIP-3009 authorization and retries
4. Your middleware forwards the signed payload to Semantic's facilitator
5. The facilitator verifies the signature, settles on-chain, and confirms
6. Your route handler runs and returns the resource

You never touch private keys, gas tokens, or on-chain transactions. You just specify the price and the address to receive funds.

## Pricing

The `price` field is a structured object that tells the buyer exactly what token to pay, how much, and on which chain. USDT0 uses 6 decimals, so `"1000"` = \$0.001.

```typescript theme={null}
price: {
  amount: "1000",                                          // base units (6 decimals)
  asset: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",     // USDT0 contract
  extra: { name: "USDT0", version: "1", decimals: 6 },    // EIP-712 domain info
}
```

The `extra` fields are passed through to the buyer's client for EIP-712 signature construction. `name` and `version` must match what the on-chain USDT0 contract expects.

## USDT0 Deployments

<CardGroup cols={2}>
  <Card color="#7B3FE4" icon="bolt" href="https://plasmascan.to/address/0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb" title="Plasma">
    `eip155:9745` · `0xB8C...5ebb`
  </Card>

  <Card color="#00C2FF" icon="coins" href="https://stablescan.xyz/address/0x779Ded0c9e1022225f8E0630b35a9b54bE713736" title="Stable">
    `eip155:988` · `0x779...3736`
  </Card>
</CardGroup>

Full deployment list at [docs.usdt0.to](https://docs.usdt0.to/technical-documentation/deployments).

## Minimal server (single chain)

```typescript theme={null}
import { config } from "dotenv";
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";

config();

// --- Config ---
const PAY_TO = process.env.PAY_TO_ADDRESS as `0x${string}`;
const FACILITATOR_URL = "https://x402.semanticpay.io/";
const PLASMA_NETWORK = "eip155:9745";
const USDT0_PLASMA = "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb";
const PRICE = "1000"; // $0.001 in base units

// --- Facilitator client ---
const facilitatorClient = new HTTPFacilitatorClient({ url: FACILITATOR_URL });

// --- Express app ---
const app = express();

app.use(
  paymentMiddleware(
    {
      "GET /weather": {
        accepts: [
          {
            scheme: "exact",
            network: PLASMA_NETWORK,
            price: {
              amount: PRICE,
              asset: USDT0_PLASMA,
              extra: { name: "USDT0", version: "1", decimals: 6 },
            },
            payTo: PAY_TO,
          },
        ],
        description: "Weather data",
        mimeType: "application/json",
      },
    },
    new x402ResourceServer(facilitatorClient).register(
      PLASMA_NETWORK,
      new ExactEvmScheme(),
    ),
  ),
);

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

app.get("/health", (req, res) => {
  res.json({ status: "ok", chain: "plasma", payTo: PAY_TO });
});

const PORT = process.env.PORT || 4021;
app.listen(PORT, () => {
  console.log(`Server listening at http://localhost:${PORT}`);
  console.log(`Network: ${PLASMA_NETWORK}`);
  console.log(`USDT0: ${USDT0_PLASMA}`);
  console.log(`Pay to: ${PAY_TO}`);
});
```

## Multi-chain server (Plasma + Stable)

Accept payments on both chains. The buyer's client picks whichever network it has funds on.

```typescript theme={null}
import { config } from "dotenv";
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";

config();

const PAY_TO = process.env.PAY_TO_ADDRESS as `0x${string}`;
const FACILITATOR_URL = "https://x402.semanticpay.io/";

// --- Network config ---
const NETWORKS = {
  plasma: {
    network: "eip155:9745" as const,
    usdt0: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
  },
  stable: {
    network: "eip155:988" as const,
    usdt0: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
  },
};

const PRICE = "1000"; // $0.001

function priceOnChain(chain: keyof typeof NETWORKS) {
  return {
    amount: PRICE,
    asset: NETWORKS[chain].usdt0,
    extra: { name: "USDT0", version: "1", decimals: 6 },
  };
}

// --- Facilitator + resource server ---
const facilitatorClient = new HTTPFacilitatorClient({ url: FACILITATOR_URL });

const resourceServer = new x402ResourceServer(facilitatorClient)
  .register(NETWORKS.plasma.network, new ExactEvmScheme())
  .register(NETWORKS.stable.network, new ExactEvmScheme());

// --- Express app ---
const app = express();

app.use(
  paymentMiddleware(
    {
      "GET /weather": {
        accepts: [
          {
            scheme: "exact",
            network: NETWORKS.plasma.network,
            price: priceOnChain("plasma"),
            payTo: PAY_TO,
          },
          {
            scheme: "exact",
            network: NETWORKS.stable.network,
            price: priceOnChain("stable"),
            payTo: PAY_TO,
          },
        ],
        description: "Weather data",
        mimeType: "application/json",
      },
    },
    resourceServer,
  ),
);

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

app.get("/health", (req, res) => {
  res.json({ status: "ok" });
});

const port = process.env.PORT || 4021;
app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});
```

## Route configuration

The first argument to `paymentMiddleware` maps routes to payment requirements. The key format is `METHOD /path`.

```typescript theme={null}
paymentMiddleware(
  {
    "GET /api/data": {
      accepts: [
        {
          scheme: "exact",
          network: "eip155:9745",
          price: {
            amount: "10000",  // $0.01
            asset: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
            extra: { name: "USDT0", version: "1", decimals: 6 },
          },
          payTo: PAY_TO,
        },
      ],
      description: "Premium data feed",
      mimeType: "application/json",
    },
    "POST /api/generate": {
      accepts: [
        {
          scheme: "exact",
          network: "eip155:9745",
          price: {
            amount: "50000",  // $0.05
            asset: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
            extra: { name: "USDT0", version: "1", decimals: 6 },
          },
          payTo: PAY_TO,
        },
      ],
      description: "AI generation endpoint",
      mimeType: "application/json",
    },
  },
  resourceServer,
);
```

Routes not listed in the config are not gated — they behave like normal Express routes. This is why `/health` works without payment in the examples above.

## Lifecycle events

The Semantic facilitator supports an optional `X-Event-Callback` header on `/verify` and `/settle` requests. When provided, the facilitator POSTs real-time lifecycle events to that URL as verification and settlement happen. This is useful for building dashboards, logging pipelines, or payment flow visualizations.

Events are fire-and-forget and do not block the facilitator's response. If the callback URL is unreachable, events are silently dropped. If no header is provided, no events are sent.

### Event types

| Type               | When                                                 | Key fields                          |
| ------------------ | ---------------------------------------------------- | ----------------------------------- |
| `verify_started`   | Facilitator begins verifying the payment             | `details.network`, `details.checks` |
| `verify_completed` | Verification finished                                | `details.isValid`                   |
| `verify_failed`    | Verification threw an error                          | `details.error`                     |
| `settle_started`   | Facilitator is broadcasting the on-chain transaction | `details.network`                   |
| `settle_completed` | Transaction confirmed on-chain                       | `details.transactionHash`           |
| `settle_failed`    | Settlement threw an error                            | `details.error`                     |

### Example: receiving events

Add a POST endpoint to your server:

```typescript theme={null}
app.post("/payment-events", (req, res) => {
  const { type, title, details } = req.body;
  console.log(`[${type}] ${title}`, details);
  res.json({ ok: true });
});
```

Then configure the facilitator client to include the callback header. Since `HTTPFacilitatorClient` from `@x402/core` doesn't support custom headers directly, wrap fetch:

```typescript theme={null}
const CALLBACK_URL = "http://localhost:4021/payment-events";

const facilitatorClient = new HTTPFacilitatorClient({
  url: FACILITATOR_URL,
  fetch: (url, init) =>
    fetch(url, {
      ...init,
      headers: {
        ...init?.headers,
        "X-Event-Callback": CALLBACK_URL,
      },
    }),
});
```

With this in place, every `/verify` and `/settle` call to the facilitator will include the callback header, and your `/payment-events` endpoint will receive events like:

```json theme={null}
{
  "type": "settle_completed",
  "step": 10,
  "title": "Settlement Confirmed",
  "description": "Payment transaction confirmed on blockchain",
  "details": {
    "success": true,
    "transactionHash": "0xabc123...",
    "network": "eip155:9745"
  },
  "actor": "blockchain",
  "target": "facilitator"
}
```

For the full event reference, see the [Facilitator API docs](/endpoints).

## Environment variables

```bash theme={null}
# .env
PAY_TO_ADDRESS=0xYourReceivingAddress
PORT=4021
```

## Using with other frameworks

x402 also provides middleware for Hono and Next.js:

```bash theme={null}
# Hono
npm install @x402/hono

# Next.js
npm install @x402/next
```

The pattern is the same: create a facilitator client, register the EVM scheme for your network(s), and apply middleware. See the [x402 examples](https://github.com/coinbase/x402/tree/main/examples) for framework-specific code.
