The hosted facilitator
Point bolthub Pay at the hosted facilitator and get at-most-once redemption, usage metering, analytics, and one-click wallet wiring. Same code, one swapped rail; funds still settle straight to your wallet.
The facilitator is the hosted back-end for @bolthub/pay
sellers. Self-hosting the SDK is free and needs no account, but it leaves two
jobs on your plate: running an invoice-capable wallet endpoint, and deduping
proofs so one payment can't be replayed for the token's whole lifetime. The
hosted facilitator takes both, plus metering and analytics, while staying out
of the funds path: invoices are minted on your connected wallet, and
payments settle directly to it.
| Self-hosted SDK | Hosted facilitator | |
|---|---|---|
| Cost | Free (MIT) | Part of the hosted plan |
| Account | None | bolthub account + connected wallet |
| Invoices | Your wallet integration | Your dashboard-connected wallet |
| Per-call billing | Proof valid for token TTL (no built-in dedup) | At-most-once redemption (replay-protected) |
| Metering | Yours to build | Metered per call, in the dashboard |
| Custody | Yours | Still yours; bolthub only sees the metering path |
Wire it up
- Connect a wallet in the dashboard (Settings → Wallet). Invoices are minted against it; sats land in it.
- Issue an API key in Settings → API keys. The key is shown once at creation; store it as a secret. Keys can carry an optional expiry and can be rotated or revoked at any time (all key events are audit-logged).
- Point the SDK at the facilitator. This is the same snippet the dashboard generates on each tool's page:
import { createPaywall, facilitatorRail, httpFacilitator } from "@bolthub/pay";
const pay = createPaywall({
rails: [facilitatorRail({
scheme: "l402",
assets: ["sat"],
transport: httpFacilitator({
baseUrl: "https://api.bolthub.ai/facilitator",
apiKey: process.env.BOLTHUB_API_KEY!, // issued in Settings → API keys
}),
})],
});
pay.tool(server, "get_satellite_image", "Recent satellite imagery", schema,
{ price: { amount: 2000, asset: "sat" } },
async (args) => ({ content: [{ type: "text", text: await fetchImage(args) }] }));Starting self-hosted and graduating later means swapping the rail entry; your tool registrations and handlers don't change.
What happens per call
- An unpaid call reaches your tool; the rail asks the facilitator to
mint (
POST /facilitator/v1/mint, authenticated with your API key). The facilitator creates a Lightning invoice on your wallet and signs a resource-scoped, time-limited token. Both go back to the buyer inside thepayment_requiredchallenge. - The buyer pays the invoice and re-calls the tool with
<token>:<preimage>as proof. - The rail sends the proof to verify (
POST /facilitator/v1/verify). The facilitator checks the signature, expiry, resource binding, and the preimage, then redeems the proof at most once: a replayed proof is rejected even though the token is still within its TTL. - The verified call is metered. Usage shows up on the Analytics page (per tool, scheme, and asset) once paid calls exist.
Payment invalidity is a normal ok: false result to the SDK; only
authentication and transport problems surface as errors. Per-key and
per-tenant rate limits protect the mint path (minting hits your wallet).
Security properties
- Only a peppered HMAC hash of your API key is stored; the plaintext exists once, in the response that issued it.
- Revocation and rotation take effect immediately on the instance that serves the request and fleet-wide within about 30 seconds; key expiry is enforced exactly.
- Token signing secrets are derived per tenant; a proof minted for one seller (or one resource) can never verify for another.
- bolthub is in the control path, never the funds path: there is no balance held for you and nothing to withdraw.
See Security for the full model, and the @bolthub/pay reference for the SDK surface.