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.
The config file
Everything non-runtime lives inconfig/facilitator.json. The file is loaded and validated at boot; changes require a server restart.
configurations[]— one facilitator process can host many merchants. Each configuration has its own Fireblocks credentials, API keys, and product catalog. Clients are routed to the right configuration by theHostheader matchingpublic_host.asset_idis the Fireblocks-native identifier (for example,USDC_BASE,USDC_POLYGON,ETH_BASE). There is no separate tokens or blockchains registry — the asset entry carries everything the facilitator needs.- Assets set
stable: truefor USD-pegged stablecoins (no network call at request time) orprice_symbol: "<coingecko-id>"for live-priced assets. - Products use
pricing[]to list accepted assets, and optionally ausd_priceto convert from. Legacy{asset_id, price}on a product is still accepted and normalized to a single-entrypricing[].
Auth model
The facilitator exposes two independent auth surfaces. They are never mixed.| Path | Auth | Purpose |
|---|---|---|
/api/admin/* | JWT (HS256 or JWKS) | Operators. Per-route scope check. |
/api/payments/* | Persistent API key | Machine clients (merchant servers, agents). Scoped to one configuration. |
/api/discovery/*, /api/health, /api/payments/supported | Public | — |
Management API (JWT)
Admin tokens are short-lived JWTs. In development, mint them locally from the HS256 secret scaffolded bynpm run setup:
X402_ADMIN_TOKEN and the x402 CLI plus any direct curl picks it up:
X402_ADMIN_JWT_JWKS_URL at your identity provider’s JWKS endpoint. Your IdP issues tokens carrying tenant_id, sub, scope (space-delimited), and optional configuration_ids.
Payment processing (API keys)
Payment API tokens are opaque bearer strings minted viax402 keys create or POST /api/admin/tokens. Only the SHA-256 hash is persisted in the config file; the plaintext is shown once at creation and never again. Keys are scoped to a single configuration — a key minted for merchant-a cannot settle payments for merchant-b.
Scopes you will actually use:
process-payments— call/api/payments/{create,verify,settle}api:read— read-only access (reserved for future public read routes)*— wildcard
Management API
The management API is the operator-facing surface. Every route is JWT-authenticated and gated by an explicit scope.Admin scopes
| Scope | Routes |
|---|---|
admin:read | GET /facilitator, GET /assets, GET /products, GET /tokens, GET /fireblocks |
admin:write | POST / DELETE on assets, products, tokens, and fireblocks test |
payments:read | GET /payments, GET /payments/:id |
payments:write | POST /payments/:id/mark-failed, POST /payments/:id/refund, POST /payments/:id/sync, POST /payments/sync-all, POST /payments/sweep-expired |
* | Wildcard — passes every admin scope check |
Targeting a configuration
When a tenant holds multiple configurations, admin requests pick one with theX-Configuration-ID header or ?configuration=<id> query parameter. If omitted, the request targets default_configuration_id:
configurationIds grant must include the chosen configuration, otherwise the request returns 403.
Inspecting configuration
GET /api/admin/facilitator returns the configuration block the principal is scoped to, with fireblocks.api_key redacted.
Assets
GET /api/admin/assets and GET /api/admin/assets/:assetId list or fetch one configured asset.
POST /api/admin/assets registers an asset. The facilitator calls Fireblocks’ listAssets to fill address / decimals / chain_id; you supply the x402-specific fields in the body.
POST /api/admin/assets/sync re-fetches Fireblocks-owned fields for every asset in scope. Body: { "apply": false } for a dry-run diff report; true to write. Returns per-asset diffs[] and any errors[].
Products
GET /api/admin/products and GET /api/admin/products/:productId list or fetch products with their asset joined.
Adding and editing products over HTTP is not yet supported. Use the
x402 products CLI. The HTTP endpoints above are read-only on purpose.API tokens
The primary write endpoints for issuing machine credentials.POST /api/admin/tokens mints a key:
GET /api/admin/tokens lists keys (hashes omitted). DELETE /api/admin/tokens/:keyId revokes a key, returning 204 on success or 404 if the key does not exist.
Inspecting payments
GET /api/admin/payments and GET /api/admin/payments/:paymentId are read-only payment inspection. Scope: payments:read. Query parameters on list: ?status=<status>, ?limit=<n>, ?offset=<n>.
Managing payments
POST /api/admin/payments/:paymentId/mark-failed forces a stuck row into failed with an operator-supplied reason. No on-chain action — for rows where the happy-path transitions never closed out (server crash mid-settle, settlement poll timed out, and similar). Scope: payments:write.
POST /api/admin/payments/:paymentId/refund refunds a payment whose on-chain leg landed (status is completed or settled). Submits a CONTRACT_CALL to the token’s transfer(to, amount) function via Fireblocks. Status transitions: refunding → refunded, or refund_failed with the Fireblocks error recorded. Returns 409 when the payment is not refundable, 502 when the on-chain refund fails. Scope: payments:write.
POST /api/admin/payments/:paymentId/sync reconciles one payment against Fireblocks. Reads the persisted fireblocksTxId, asks Fireblocks for the transaction’s current state, and transitions the row:
completedwithtransactionHashandblockNumberon FireblocksCOMPLETED.failedon FireblocksFAILED,CANCELLED,BLOCKED, orREJECTED.- No-op while the transaction is still in-flight.
POST /api/admin/payments/sync-all runs the same reconcile pass across every settling row in the caller’s scope. The same pass fires at boot for every configuration (processing-role only); opt out with X402_RECONCILE_ON_BOOT=false when running many processing instances against a shared store.
POST /api/admin/payments/sweep-expired bulk-expires every pending payment whose expiresAt is in the past. Idempotent per row — safe to run on a schedule.
Payment statuses
Payments transition through:pending → verified → settling → completed, plus the terminal states settled, refunding, refunded, refund_failed, expired, and failed.
Reconciler-ownership invariant. Once a
fireblocksTxId is attached to a row, the settle route never preempts it into failed on transient errors. The underlying transaction may still land, so the row stays settling and the reconciler decides terminality from Fireblocks’s own truth. Rows without a fireblocksTxId still follow the normal settle path.CLI reference
The facilitator ships two CLI surfaces with a clean split:x402— a remote HTTP client for/api/admin/*. Pure API client, safe to run from anywhere withX402_ADMIN_TOKENset. TargetsX402_URL(defaulthttp://localhost:3000).npm run setup*— local bootstrap scripts. Touch the filesystem (scaffold config, mint JWTs from the on-disk secret, import a legacy SQLite database). Must run on the same host as the server.
x402 remote CLI
Install once per developer:-c, --configuration <id> (or env CONFIGURATION=<id>); omitted means the server default.
--url <base> and --token <jwt> to override the env defaults.
Local setup scripts
These run throughnpm run. They never hit HTTP.
x402 products add while the server is running (nodemon reloads).
End-to-end test harness
eip-3009, permit2, erc7710) in sequence against the test client, polls the admin API until each payment row reaches completed, and prints a pass/fail summary with block explorer links. Exits 0 if every mechanism passed. Idempotent — safe to re-run.
Payment Instruction Integrity
Optional. When enabled on a configuration, the facilitator signs every/api/payments/create response with an ES256 keypair and attaches the envelope two ways:
- As an
integritytop-level field on the JSON body. - As an
X-402-Integrityresponse header (mirrored by@x402/express).
did:web document referenced by the envelope, verify the signature, and refuse to sign any payment whose body was altered between the facilitator and the wallet.
What the facilitator signs
The envelope covers a payment-critical slice of the 402 body — specifically{x402Version, accepts} — plus the envelope’s own iat and exp. resource.url, error, and extensions are intentionally excluded because the merchant SDK rewrites resource.url to its own public origin before emitting the 402; signing it would invalidate every response. The payment data the wallet actually commits to (amount, asset, payTo, network, scheme — all in accepts[]) is signed.
Canonical bytes:
Envelope shape
Base64url-encoded JSON:Enabling it
npm run setup scaffolds ./secrets/integrity-p256.pem (a P-256 keypair) for you. Flip integrity on per configuration:
integrity.serve_did_document: true, the facilitator serves the DID document at GET /.well-known/did.json (resolved by Host header against public_host). If you would rather host the did.json yourself, leave serve_did_document: false and publish the public key at whatever URL did:web:<domain> resolves to.
Wallet-side verification
The bundled test client has aVERIFY_INTEGRITY=true flag that decodes the envelope, checks iat and exp, resolves did:web:<domain>, verifies the ES256 signature, and aborts the flow if verification fails.
REQUIRE_INTEGRITY=true to also reject quotes that do not carry an envelope at all.
Scope and limitations
What integrity signs:{x402Version, accepts, iat, exp}. What it does not sign: resource.url, error, extensions — these are mutable by the merchant SDK.
Supported algorithm: ES256 (P-256). Not yet supported: ES256K, EdDSA, did:webvh, multiple keys per DID, on-chain registry binding.
The PII specification is a draft; the envelope format follows it, but the multi-accept canonical form is a documented extension.
Running in production
Environment variables
All are optional unless noted.| Variable | Default | Notes |
|---|---|---|
PORT | 3000 | Server listen port |
CONFIG_PATH | ./config/facilitator.json | Where to load config |
PAYMENT_STORE | sqlite | memory, sqlite, or postgres |
DB_PATH | ./data/facilitator.db | sqlite only |
POSTGRES_URL | — | Required when PAYMENT_STORE=postgres |
ALLOWED_ORIGINS | http://localhost:$PORT | Comma-separated CORS allowlist |
X402_ADMIN_JWT_SECRET | — | Inline HS256 secret; takes precedence over the file-based default |
X402_ADMIN_JWT_SECRET_FILE | ./secrets/jwt-hs256.key | File-based HS256 secret; scaffolded by npm run setup |
X402_ADMIN_JWT_JWKS_URL | — | JWKS endpoint for production (RS256 or ES256); overrides HS256 when set |
X402_ADMIN_JWT_ISSUER | — | Optional iss claim to require on every admin JWT |
X402_ADMIN_JWT_AUDIENCE | — | Optional aud claim to require on every admin JWT |
X402_URL | http://localhost:3000 | Read by the x402 CLI |
X402_ADMIN_TOKEN | — | Bearer JWT for the x402 CLI |
X402_ALLOW_MAINNET | — | Must be true to register or run with mainnet assets. Default-deny. |
X402_RECONCILE_ON_BOOT | true | Set to false when running many processing instances against a shared store. |
COINGECKO_API_KEY | — | Optional; enables CoinGecko Pro. Free tier works unauthenticated. |
NODE_ENV | development | Standard Node environment |
config/facilitator.json.
Network policy
The facilitator tracks every asset’s network via anis_testnet field (populated at import time from Fireblocks’ blockchain.onchain.test). By default, mainnet is denied:
- Boot — the server refuses to start if any configured asset has
is_testnet: falseandX402_ALLOW_MAINNETis not set totrue. The error lists the offending(asset_id, chain_id)pairs. - Import —
POST /api/admin/assets(andx402 assets import) returns 403 when the hydrated asset is mainnet and the flag is off.
network policy: mainnet OK (X402_ALLOW_MAINNET=true) or testnet-only (default) at startup, so there is no ambiguity.
Build and run compiled
X402_ADMIN_JWT_JWKS_URL at your JWT issuer’s public keys. HS256 works but asymmetric or JWKS is the right choice at scale. Without either, the admin API rejects everything.
Role split
The same binary runs in three profiles, selected byX402_ROLE:
processing— handles/api/payments/*and the reconciler. Run multiple of these behind a load balancer.management— handles/api/admin/*. Smaller surface, separate scaling.all(default) — both. Right for single-instance and development deployments.
processing instances against a shared payment store, set X402_RECONCILE_ON_BOOT=false on all but one so only one instance leads the boot-time reconcile pass.
Multiple merchants on one facilitator
Payment-service providers who operate the facilitator on behalf of many merchants use one configuration per merchant:Host header against each configuration’s public_host. API keys are bound to the configuration that issued them — a key from merchant-a cannot settle payments for merchant-b.
For a single-merchant deployment, leave the default single-configuration layout in place.
Operator responsibilities and risk
Running an x402 facilitator means initiating real on-chain value transfers from a Fireblocks vault you control. Before deploying, read the full operator-facing disclaimer:DISCLAIMER.md— covers irreversibility of on-chain transfers, the absence of a third-party security audit at publication, third-party contract risk (Permit2, the MetaMask Delegation Framework, ERC-20 tokens including USDC and USDT blacklisting and pause functions), the non-advisory nature of the code, jurisdictional and data-protection considerations (including GDPR and UK GDPR), the Fireblocks trademark policy, the absence of any fiduciary or custodial relationship, and the independence of the x402 protocol specification.
License
The facilitator is licensed under Apache License 2.0. SeeLICENSE for the full legal text. The license does not grant rights to use the Fireblocks name, logos, or other trademarks — see the trademark clause in DISCLAIMER.md.