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

# x402 Merchant Integration

> Charge Canton Coin or a CIP-56 token per HTTP request with x402 middleware, on Express or Next.js.

Add x402 to your API to charge Canton Coin (CC) or a CIP-56 registry token (e.g. USDCx) per request. Callers pay before your handler runs. **You do not need a token balance** - you receive, you do not send.

## Quickstart

Four steps to a paid endpoint. Details are in [Reference](#reference) below.

<Steps>
  <Step title="Provision a TransferPreapproval (once)">
    Your party needs a live `TransferPreapproval` so incoming payments settle in one transaction. Provision it with the CLI (self-custody wallet), passing the Canton Coin admin (the DSO party, from your Scan API):

    ```bash theme={null}
    npm i -g @ftptech/canton-agent-wallet@latest
    canton-agent-wallet preapproval --admin DSO::1220... --days 180
    # → TransferPreapproval created (updateId ...)
    ```
  </Step>

  <Step title="Install the middleware">
    ```bash theme={null}
    # Express
    npm i @ftptech/x402-canton-core @ftptech/x402-canton-express
    # Next.js App Router
    npm i @ftptech/x402-canton-core @ftptech/x402-canton-next
    ```
  </Step>

  <Step title="Gate a route">
    ```typescript theme={null}
    import express from "express";
    import { cantonPaymentMiddleware } from "@ftptech/x402-canton-express";

    const paymentReq = {
      scheme: "exact",
      network: "canton:mainnet",
      amount: "10000000000",              // atomic units: 1 CC = 10^10
      asset: "CC",
      payTo: "<YOUR_PARTY>::1220...",     // CC lands here
      maxTimeoutSeconds: 120,
      extra: {
        assetTransferMethod: "transfer-factory",
        feePayer: "<facilitator-party>::1220...",   // from GET /supported
        synchronizerId: "global-domain::1220...",   // from GET /supported
        instrumentId: { admin: "<DSO-party>::1220...", id: "Amulet" },
        executeBeforeSeconds: 120,
      },
    } as const;

    const app = express();
    app.use(express.json());
    app.use(
      cantonPaymentMiddleware({
        facilitatorUrl: "https://facilitator.ftptech.xyz",
        routes: { "POST /api/data": { accepts: [paymentReq] } },
      }),
    );

    app.post("/api/data", (req, res) => res.json({ result: "..." })); // runs only after payment
    app.listen(3000);
    ```
  </Step>

  <Step title="Verify">
    ```bash theme={null}
    curl -si https://your-api.com/api/data -H "Content-Type: application/json" -d '{}'
    # HTTP/2 402
    # payment-required: <base64>
    ```

    Decode to confirm your values: `echo "<base64>" | base64 -d | jq '.accepts[0]'`
  </Step>
</Steps>

## How it works

The payer signs a `TransferFactory_Transfer` naming you as receiver but does not submit it. The facilitator relays it and pays the traffic fee; your standing `TransferPreapproval` makes it settle directly to you in one transaction. No escrow, no custody. CC and registry tokens use the same flow, differing only by `extra.instrumentId`. See [How Payments Work](/x402-payments) for the full flow, error codes, and replay protection.

## Reference

### Facilitator parameters

`feePayer` and `synchronizerId` come from `GET /supported`; the DSO party (for `instrumentId.admin`) comes from your Canton Scan API.

```bash theme={null}
curl https://facilitator.ftptech.xyz/supported
# signers["canton:*"][0]  -> feePayer
# extra.synchronizerId    -> synchronizerId
```

`synchronizerId` may be omitted from `extra`; clients fall back to the advertised value.

### TransferPreapproval

Without a live preapproval the transfer resolves to a two-step pending `TransferInstruction`, `/settle` returns `invalid_exact_canton_preapproval_missing`, and callers get a 402 instead of the resource. A preapproval is time-bounded, so renew it before expiry. There is no fixed maximum; a longer `--days` prepays a slightly larger one-time fee (about 0.016 CC/day, so 180 days is roughly 3 CC). `--expires-at <ISO>` overrides `--days`.

To charge a **registry token**, provision one preapproval per instrument and pass its registrar as `--admin`:

```bash theme={null}
canton-agent-wallet preapproval --admin <registrar>::1220... --id USDCx
```

The registrar must be trusted (`CANTON_AGENT_REGISTRY_TRUSTED_PARTIES`; USDCx is built in).

**Hosted-node merchant:** if your party lives on your own Canton node, create the `TransferPreapproval` through that wallet/validator directly (receiver = your party). No relay or CLI needed, and no custom DAR to install - `transfer-factory` runs through the standard CIP-56 `:TransferInstruction` interface.

### PaymentRequirements fields

* `amount`: integer string of atomic units (1 CC = 10^10). Compared by value, so `"100000000"` = 0.01 CC; a mismatch returns `invalid_exact_canton_amount_mismatch`.
* `feePayer`: the facilitator party. Clients MUST NOT alter it.
* `instrumentId`: the token charged, and authoritative over `asset` (a display symbol). CC is `{ admin: "<DSO-party>", id: "Amulet" }`; a registry token is `{ admin: "<registrar>", id: "<instrument-id>" }`.
* `executeBeforeSeconds`: relative deadline (seconds) the client uses to compute the transfer's absolute `executeBefore`.
* `memo` (optional): a reconciliation tag stamped into the transfer metadata; not validated.

### Next.js App Router

```typescript theme={null}
// app/api/data/route.ts
import { withCantonPayment } from "@ftptech/x402-canton-next";

export const POST = withCantonPayment(
  async (req) => Response.json({ result: "..." }),
  { accepts: [paymentReq], facilitatorUrl: "https://facilitator.ftptech.xyz" },
);
```

Express route keys use `"METHOD /path"`, matched exactly against `${req.method} ${req.path}`. Unregistered routes pass through ungated.

### Request header size

The paid request carries the signed transfer in the `X-PAYMENT` header - \~8-10 KB on Canton (larger than other chains, and more if the payment draws on several holdings). Raise your ingress header limit or the paid retry fails with `400`/`431` before reaching your app:

* **nginx:** `large_client_header_buffers 4 16k;` (default `4 8k`)
* **Node.js:** `--max-http-header-size=32768` (default is a 16 KB total cap)

### Security notes

* **Your `accepts[]` is authoritative.** The middleware ignores any amount the client claims; a client cannot pay 0.01 CC for a 1 CC resource by tampering.
* **The proven payer is on-ledger.** The facilitator binds the payer to the signed transfer's sender, not to any payload field, so a spoofed `payer` cannot impersonate.
* **Settlement happens before your handler.** If your handler throws after `/settle`, the payer already paid. Handle errors gracefully; do not retry settlement.
* **Keep `payTo` correct.** Funds go where `payTo` says. If your party changes, update `paymentReq` and provision a new `TransferPreapproval`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.