Skip to main content
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 below.
1

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):
2

Install the middleware

3

Gate a route

4

Verify

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

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 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.
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:
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

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.