Skip to main content

Documentation Index

Fetch the complete documentation index at: https://developers.fireblocks.com/llms.txt

Use this file to discover all available pages before exploring further.

Open-source or hosted? This page describes the open-source facilitator that you run yourself. Fireblocks also offers a fully managed, fully secured hosted x402 Facilitator — production-grade security, operational support, monitoring, and a managed endpoint, with no infrastructure for you to run or upgrade. Talk to us about early access.
This page covers how to wire the facilitator into a merchant server: the middleware pattern, the four endpoints of the payment processing API, when to settle, the four transfer mechanisms, and how to price products. For the high-level concept and a quick-start bootstrap, see the Overview.

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:
  1. On every request, inspect the incoming path. If the path is payment-gated and lacks a payment-signature header, return 402 with a JSON body. Your middleware builds this from a product declared in your config.
  2. On a request that carries a payment-signature header, call this facilitator’s POST /api/payments/verify to check the signature, then POST /api/payments/settle to run the on-chain transaction. If both succeed, serve your resource.
The request your middleware sends to the facilitator:
curl -X POST https://facilitator.example.com/api/payments/verify \
  -H "Authorization: Bearer $MERCHANT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "paymentPayload":      { /* the client-signed payload */ },
    "paymentRequirements": { /* what you advertised in the 402 */ }
  }'
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.
curl -X POST https://facilitator.example.com/api/payments/create \
  -H "Authorization: Bearer $MACHINE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"product_id":"prod_7Lm3nOp"}'

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.
curl -X POST https://facilitator.example.com/api/payments/verify \
  -H "Authorization: Bearer $MERCHANT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "paymentPayload":      { /* signed payload from the client */ },
    "paymentRequirements": { /* what you advertised in the 402 */ }
  }'
Response shape:
{ "isValid": true, "payer": "0x...", "invalidReason": null }

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.
{ "success": true, "txHash": "0x...", "networkId": "base-mainnet" }

When to settle

Your middleware controls the order:
  • Optimistic settlement — serve the response first, call /settle in the background. Simpler, preferred when the resource is idempotent or cheap.
  • Settle-first — call /settle synchronously 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 a transfer_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.
MechanismUse it when
eip-3009The token natively supports transferWithAuthorization() (notably USDC). Single transaction, no helper contract. The default for USDC.
permit2You need a universal ERC-20 path that does not depend on the token implementing 3009. Settled through the Uniswap-deployed Permit2 contract via the x402ExactPermit2Proxy.
upto-permit2The merchant needs to charge a variable amount within a client-signed ceiling — metered API calls billed at the end, for example. The facilitator picks the actual charge up to the signed maximum.
erc7710The payer is a smart account (or an EOA upgraded via EIP-7702) authorizing through the MetaMask Delegation Framework. Settled with DelegationManager.redeemDelegations().
upto-permit2 lets the facilitator select the actual charge amount up to the user’s signed maximum. Your pricing logic is solely responsible for staying within that ceiling and for charging amounts the payer would expect. There is no protocol-level cap below the signed maximum.

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.
The facilitator enforces the chosen mechanism on /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 a pricing[] table listing one entry per accepted asset. Each entry is one of:
  • Native-denominationamount is set. The client is charged exactly amount base units of that asset, regardless of exchange rates. Useful when you bill in crypto directly.
  • USD-convertedamount is null. The product’s usd_price (fractional dollars) is converted to this asset at request time using a PriceProvider.

Mixing both on one product

A product can mix native and USD-converted entries in the same pricing[]:
{
  "usd_price": 0.10,
  "pricing": [
    { "asset_id": "USDC_BASE" },                       // convert $0.10 → USDC
    { "asset_id": "USDC_POLYGON", "amount": 90000 },   // override: pay 0.09 USDC on Polygon
    { "asset_id": "ETH_BASE" }                         // convert $0.10 → ETH at the current rate
  ]
}

Multiple transfer mechanisms on the same asset

Each pricing row accepts an optional transfer_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.
{
  "usd_price": 0.10,
  "pricing": [
    { "asset_id": "USDC_BASE" },                                     // inherit → eip-3009
    { "asset_id": "USDC_BASE", "transfer_mechanism": "permit2" },    // same asset, permit2
    { "asset_id": "USDC_BASE", "transfer_mechanism": "erc7710" }     // same asset, MDF delegation
  ]
}

PriceProvider

The bundled PriceProvider stack is a CompositePriceProvider that tries in order:
  1. StableOnlyPriceProvider — any asset marked stable: true quotes at 1:1 USD. No network call.
  2. CoinGeckoPriceProvider — queries CoinGecko /simple/price using the asset’s price_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 set COINGECKO_API_KEY.
Swap for Pyth, Chainlink, or a merchant-owned oracle by implementing the 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 1ormoreingas;thesametransactiononBaseisfractionsofacent.Amerchantwhoquotes"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. The GasCostEstimator 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 from accepts[].
See src/services/pricing/GasCostEstimator.ts in the repo for the interfaces.
  • Operating and production — the config file, auth model, management API, CLI reference, Payment Instruction Integrity, production deployment, and operator responsibilities.