Integrating from your merchant server
Your server keeps serving its own traffic. You add middleware (Express, Next.js, Hono, or any equivalent — the same pattern as the Coinbase x402 SDK packages) that does two things:- On every request, inspect the incoming path. If the path is payment-gated and lacks a
payment-signatureheader, return 402 with a JSON body. Your middleware builds this from a product declared in your config. - On a request that carries a
payment-signatureheader, call this facilitator’sPOST /api/payments/verifyto check the signature, thenPOST /api/payments/settleto run the on-chain transaction. If both succeed, serve your resource.
POST /api/payments/settle takes the same body shape.
The facilitator never sees the end-user’s HTTP request. It only handles the cryptographic verification and the Fireblocks CONTRACT_CALL.
The payment processing API
These are the endpoints your merchant server (or any machine client) hits once you have given it an API key. All endpoints under/api/payments/* are authenticated with the merchant API key you minted via x402 keys create or POST /api/admin/tokens — except /api/payments/supported, which is public.
GET /api/payments/supported
Public discovery endpoint. Returns the schemes, networks, and extensions this facilitator accepts.
POST /api/payments/create
Returns a 402 PaymentRequired payload for a product without being gated behind a 402 flow. Useful for clients that want the quote before making the real request.
POST /api/payments/verify
Validates an EIP-712 signature off-chain without moving funds. Use this as a cheap pre-flight to confirm the signature is well-formed, the authorization covers the advertised price, and the payer signed the expected payload.
POST /api/payments/settle
Verifies the signature, then submits the on-chain transfer through Fireblocks. The request body matches /verify. The facilitator persists the Fireblocks transaction ID before polling for confirmation, so a process restart never loses track of an in-flight settlement.
When to settle
Your middleware controls the order:- Optimistic settlement — serve the response first, call
/settlein the background. Simpler, preferred when the resource is idempotent or cheap. - Settle-first — call
/settlesynchronously before serving. Safer for one-shot, expensive deliveries where a refund is not practical.
/api/payments/settle is a single atomic operation on the facilitator side. The choice of when to invoke it is a middleware concern, not a facilitator one.
Transfer mechanisms
Every asset registered on the facilitator declares atransfer_mechanism. The mechanism controls how signatures are validated and how the on-chain transfer is executed. A product can offer the same asset under multiple mechanisms; the facilitator emits one accepts[] entry per pricing row in the 402 response.
Picking a mechanism
- If you accept USDC and your customers hold standard USDC, choose
eip-3009. - If you accept other ERC-20s, or you want a single code path that works across tokens, choose
permit2. - If your billing model is metered or usage-based, choose
upto-permit2. - If your customers transact from smart accounts or want delegation-based authorization, choose
erc7710.
/verify and /settle. A client cannot upgrade from eip-3009 to permit2 by shipping a different payload shape — if the resolved mechanism is not in the product’s pricing[], the request returns 400 unsupported_scheme. Requests missing extra.productId (that is, requests not originated through /api/payments/create) are rejected.
Pricing modes
Each product has apricing[] table listing one entry per accepted asset. Each entry is one of:
- Native-denomination —
amountis set. The client is charged exactlyamountbase units of that asset, regardless of exchange rates. Useful when you bill in crypto directly. - USD-converted —
amountis null. The product’susd_price(fractional dollars) is converted to this asset at request time using aPriceProvider.
Mixing both on one product
A product can mix native and USD-converted entries in the samepricing[]:
Multiple transfer mechanisms on the same asset
Each pricing row accepts an optionaltransfer_mechanism override. When present, it takes precedence over the asset’s own transfer_mechanism — so a single asset can be offered under several mechanisms at once, and the 402 emits one accepts[] entry per row.
PriceProvider
The bundledPriceProvider stack is a CompositePriceProvider that tries in order:
StableOnlyPriceProvider— any asset markedstable: truequotes at 1:1 USD. No network call.CoinGeckoPriceProvider— queries CoinGecko/simple/priceusing the asset’sprice_symbol(for example,ethereum,weth,matic-network). 30-second in-memory cache; serves stale prices for up to 5 minutes if the live call fails. Pro-tier users can setCOINGECKO_API_KEY.
PriceProvider interface (src/services/pricing/PriceProvider.ts) and passing it into PricingService at startup.
Assets that cannot be priced at all (no stable flag, no price_symbol, live provider down) are dropped from the 402 response, not rejected — other accepted assets remain available to the client.
Gas cost — placeholder today
Ethereum mainnet settlement can cost $1 or more in gas; the same transaction on Base is fractions of a cent. A merchant who quotes "$0.10” should not accept mainnet payments at $0.10, or they lose money to settlement gas. TheGasCostEstimator interface and NoopGasCostEstimator are wired into the pricing pipeline today but return 0 on every chain. When you are ready to enable chain-aware pricing, implement GasCostEstimator.estimate(chainId, mechanism) (using your RPC, Chainlink gas oracles, or Fireblocks fee estimates) and pick a GasCostPolicy:
ignore— the current default.add-to-quote— gross up the client’s charge so merchant revenue is constant.reject-if-above-pct— drop uneconomic chains fromaccepts[].
src/services/pricing/GasCostEstimator.ts in the repo for the interfaces.
What to read next
- Operating and production — the config file, auth model, management API, CLI reference, Payment Instruction Integrity, production deployment, and operator responsibilities.