The payment flow
A paid request end to end under exact-canton — from the 402 your route returns to the on-ledger update id it settles with. This is what runs underneath the route you gated in Getting started, and The facilitator then zooms in on steps 5 and 6. You write almost none of it: the merchant side (steps 1, 5 and 6) is handled via @chainsafe/x402-express, and the payer side (steps 2–4) via the payer's own SDK, @chainsafe/x402-server-sdk. You configure two things yourself: your route's price and terms in the x402-express middleware (step 1), and the max header size on your resource server (step 4).
Client hits a gated route (402)
Your route answers
402 Payment Requiredwith{ x402Version: 2, accepts: [...] }. Each entry carriesscheme,network,maxAmountRequired,asset.instrumentId,payTo(your Canton party),resource, a freshnonce, and avalidBefore(300 seconds out by default).Payer builds the transfer
The payer queries its Amulet holdings and fetches a
TransferFactoryfrom the Splice Token Standard registry for the asset — the validator scan-proxy for Amulet.Prepare and sign
The payer calls Canton's
interactive-submission/prepareto get the transaction blob and its hash, then Ed25519-signs that hash with its party key. Nothing is executed, and the private key never leaves the payer.Retry with
X-PAYMENTThe payload —
payer,preparedTransaction,preparedTransactionHash,partySignature,publicKey,requirementsHash,hashingSchemeVersion— plus the requirements it was built against are JSON-encoded, base64'd into anX-PAYMENTheader, and the request is retried. That header carries the prepared transaction and its disclosed contracts.Merchant verifies, then settles
Your middleware re-checks the payment against your own terms — scheme, network,
payTo, resource, asset, and that it is neither underpriced nor expired — using the exact requirements the payment was built against, which the payload'srequirementsHashbinds, so a payment built for other terms can't pass. It then POSTs{ x402Version, paymentPayload, paymentRequirements }to/v2/verifyand, withsettle: true(the default isfalse: verify only), to/v2/settle, with your API key. A payment that fails your terms or the facilitator's verify gets a fresh challenge (a new402) instead of being settled.Facilitator executes on Canton
/v2/settlere-runs every verify check, then calls Canton'sexecuteAndWaitForTransactionwith the payer-signed blob. On success it returns the update id astransaction, which the middleware puts on your response asX-PAYMENT-RESPONSE, and your handler runs with the verified payer onreq.x402.
Raise your server's max header size
The X-PAYMENT header from step 4 runs to tens of KB — past Node's ~16 KB default — so a Node/Express merchant server must set maxHeaderSize (e.g. --max-http-header-size) or the paid retry dies with 431 Request Header Fields Too Large before your handler ever runs. Step 6 of Getting started shows where it goes.