{"id":47572572,"url":"https://github.com/forgesworn/toll-booth","last_synced_at":"2026-04-02T11:22:40.254Z","repository":{"id":341053537,"uuid":"1168712448","full_name":"forgesworn/toll-booth","owner":"forgesworn","description":"Any API becomes a Lightning toll booth in one line. L402 middleware for Express, Hono, Deno, Bun, and Workers.","archived":false,"fork":false,"pushed_at":"2026-03-24T22:42:04.000Z","size":2391,"stargazers_count":0,"open_issues_count":4,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-26T04:13:28.328Z","etag":null,"topics":["ai-agents","api-monetization","bitcoin","bun","cashu","deno","ecash","express","hono","http-402","l402","lightning","macaroon","machine-payments","micropayments","middleware","nwc","stablecoin","typescript","x402"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@thecryptodonkey/toll-booth","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/forgesworn.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"custom":["https://github.com/forgesworn/toll-booth#support"]}},"created_at":"2026-02-27T17:56:49.000Z","updated_at":"2026-03-24T22:42:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/forgesworn/toll-booth","commit_stats":null,"previous_names":["thecryptodonkey/toll-booth","forgesworn/toll-booth"],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/forgesworn/toll-booth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forgesworn%2Ftoll-booth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forgesworn%2Ftoll-booth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forgesworn%2Ftoll-booth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forgesworn%2Ftoll-booth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/forgesworn","download_url":"https://codeload.github.com/forgesworn/toll-booth/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forgesworn%2Ftoll-booth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31305294,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T09:48:21.550Z","status":"ssl_error","status_checked_at":"2026-04-02T09:48:19.196Z","response_time":89,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","api-monetization","bitcoin","bun","cashu","deno","ecash","express","hono","http-402","l402","lightning","macaroon","machine-payments","micropayments","middleware","nwc","stablecoin","typescript","x402"],"created_at":"2026-03-30T21:00:21.934Z","updated_at":"2026-04-02T11:22:40.244Z","avatar_url":"https://github.com/forgesworn.png","language":"TypeScript","readme":"# toll-booth\n\n**Nostr:** [`npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2`](https://njump.me/npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2)\n\n[![CI](https://github.com/forgesworn/toll-booth/actions/workflows/ci.yml/badge.svg)](https://github.com/forgesworn/toll-booth/actions/workflows/ci.yml)\n[![MIT licence](https://img.shields.io/badge/licence-MIT-blue.svg)](./LICENSE)\n[![Nostr](https://img.shields.io/badge/Nostr-Zap%20me-purple)](https://primal.net/p/npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2)\n[![npm](https://img.shields.io/npm/v/@forgesworn/toll-booth)](https://www.npmjs.com/package/@forgesworn/toll-booth)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.8-blue)](https://www.typescriptlang.org/)\n[![Node](https://img.shields.io/badge/Node-%3E%3D18-green)](https://nodejs.org/)\n\n**Monetise any API with one line of code.**\n\n![toll-booth demo](demo/toll-booth-demo.gif)\n\n[Live demo](https://jokes.trotters.dev/) - pay 21 sats, get a joke. No account. No sign-up. ([API](https://jokes.trotters.dev/api/joke))\n\n### Try it now\n\n```bash\nnpx @forgesworn/toll-booth demo\n```\n\nSpins up a fully working L402-gated joke API on localhost. Mock Lightning backend, in-memory storage, zero configuration. Scan the QR code from your terminal when the free tier runs out.\n\n---\n\n## Minimal example\n\n```typescript\nimport express from 'express'\nimport { Booth } from '@forgesworn/toll-booth'\nimport { phoenixdBackend } from '@forgesworn/toll-booth/backends/phoenixd'\n\nconst app = express()\nconst booth = new Booth({\n  adapter: 'express',\n  backend: phoenixdBackend({ url: 'http://localhost:9740', password: process.env.PHOENIXD_PASSWORD! }),\n  pricing: { '/api': 10 },           // 10 sats per request\n  upstream: 'http://localhost:8080',  // your existing API\n})\n\napp.get('/invoice-status/:paymentHash', booth.invoiceStatusHandler as express.RequestHandler)\napp.post('/create-invoice', booth.createInvoiceHandler as express.RequestHandler)\napp.use('/', booth.middleware as express.RequestHandler)\n\napp.listen(3000)\n```\n\n---\n\n## The old way vs toll-booth\n\n| | The old way | With toll-booth |\n|---|---|---|\n| **Step 1** | Create a Stripe account | `npm install @forgesworn/toll-booth` |\n| **Step 2** | Verify your identity (KYC) | Set your pricing: `{ '/api': 10 }` |\n| **Step 3** | Integrate billing SDK | `app.use(booth.middleware)` |\n| **Step 4** | Build a sign-up page | Done. No sign-up page needed. |\n| **Step 5** | Handle webhooks, refunds, chargebacks | Done. Payments are final. |\n\n---\n\n## Five zeroes\n\nZero accounts. Zero API keys. Zero chargebacks. Zero KYC. Zero vendor lock-in.\n\nYour API earns money the moment it receives a request. Clients pay with Lightning, Cashu ecash, or NWC — no relationship with you required. Payments settle instantly and are cryptographically final — no disputes, no reversals, no Stripe risk reviews.\n\n---\n\n## See it in production\n\n[**satgate**](https://github.com/TheCryptoDonkey/satgate) is a pay-per-token AI inference proxy built on toll-booth. It monetises any OpenAI-compatible endpoint — Ollama, vLLM, llama.cpp — with one command. Token counting, model pricing, streaming reconciliation, capacity management. Everything else — payments, credits, free tier, macaroon auth — is toll-booth.\n\n~400 lines of product logic on top of the middleware. That's what \"monetise any API with one line of code\" looks like in practice.\n\n---\n\n## Let AI agents pay for your API\n\ntoll-booth is the **server side** of a two-part stack for machine-to-machine payments.\n[402-mcp](https://github.com/forgesworn/402-mcp) is the **client side** - an MCP server that gives AI agents the ability to discover, pay, and consume L402-gated APIs autonomously.\n\n```\nAI Agent -\u003e 402-mcp -\u003e toll-booth -\u003e Your API\n```\n\nAn agent using Claude, GPT, or any MCP-capable model can call your API, receive a 402 payment challenge, pay the Lightning invoice from its wallet, and retry - all without human intervention. No OAuth dance, no API key rotation, no billing portal.\n\n---\n\n## Live demo\n\nVisit [jokes.trotters.dev](https://jokes.trotters.dev/) in a browser to try it - get a free joke, hit the paywall, scan the QR code or pay with a browser wallet extension.\n\nOr use the API directly:\n\n```bash\n# Get a free joke (1 free per day per IP)\ncurl https://jokes.trotters.dev/api/joke\n\n# Free tier exhausted - request a Lightning invoice for 21 sats\ncurl -X POST https://jokes.trotters.dev/create-invoice\n\n# Pay the invoice with any Lightning wallet, then authenticate\ncurl -H \"Authorization: L402 \u003cmacaroon\u003e:\u003cpreimage\u003e\" https://jokes.trotters.dev/api/joke\n```\n\n---\n\n## Features\n\n- **L402 protocol** - industry-standard HTTP 402 payment flow with macaroon credentials\n- **Multiple Lightning backends** - Phoenixd, LND, CLN, LNbits, NWC (any Nostr Wallet Connect wallet)\n- **Alternative payment methods** - Cashu ecash tokens and xcashu (NUT-24) direct-header payments\n- **IETF Payment authentication** - implements [draft-ryan-httpauth-payment-01](https://datatracker.ietf.org/doc/draft-ryan-httpauth-payment/), the emerging standard for HTTP payment authentication. HMAC-bound stateless challenges with Lightning settlement.\n- **x402 stablecoin payments** - accepts [x402](https://x402.org) on-chain stablecoin payments (USDC on Base, Polygon) alongside Lightning and Cashu simultaneously\n- **Cashu-only mode** - no Lightning node required; ideal for serverless and edge deployments\n- **Credit system** - pre-paid balance with volume discount tiers\n- **Free tier** - configurable daily allowance (IP-hashed, no PII stored)\n- **Privacy by design** - no personal data collected or stored; IP addresses are one-way hashed with a daily-rotating salt before any processing\n- **Self-service payment page** - QR codes, tier selector, wallet adapter buttons\n- **SQLite persistence** - WAL mode, automatic invoice expiry pruning\n- **Three framework adapters** - Express, Web Standard (Deno/Bun/Workers), and Hono\n- **Framework-agnostic core** - use the `Booth` facade or wire handlers directly\n\n---\n\n## Quick start\n\n```bash\nnpm install @forgesworn/toll-booth\n```\n\n### Express\n\n```typescript\nimport express from 'express'\nimport { Booth } from '@forgesworn/toll-booth'\nimport { phoenixdBackend } from '@forgesworn/toll-booth/backends/phoenixd'\n\nconst app = express()\napp.use(express.json())\n\nconst booth = new Booth({\n  adapter: 'express',\n  backend: phoenixdBackend({\n    url: 'http://localhost:9740',\n    password: process.env.PHOENIXD_PASSWORD!,\n  }),\n  pricing: { '/api': 10 },           // 10 sats per request\n  upstream: 'http://localhost:8080',  // your API\n  rootKey: process.env.ROOT_KEY,      // 64 hex chars, required for production\n})\n\napp.get('/invoice-status/:paymentHash', booth.invoiceStatusHandler as express.RequestHandler)\napp.post('/create-invoice', booth.createInvoiceHandler as express.RequestHandler)\napp.use('/', booth.middleware as express.RequestHandler)\n\napp.listen(3000)\n```\n\n### Web Standard (Deno / Bun / Workers)\n\n```typescript\nimport { Booth } from '@forgesworn/toll-booth'\nimport { lndBackend } from '@forgesworn/toll-booth/backends/lnd'\n\nconst booth = new Booth({\n  adapter: 'web-standard',\n  backend: lndBackend({\n    url: 'https://localhost:8080',\n    macaroon: process.env.LND_MACAROON!,\n  }),\n  pricing: { '/api': 5 },\n  upstream: 'http://localhost:8080',\n})\n\n// Deno example\nDeno.serve({ port: 3000 }, async (req: Request) =\u003e {\n  const url = new URL(req.url)\n  if (url.pathname.startsWith('/invoice-status/'))\n    return booth.invoiceStatusHandler(req)\n  if (url.pathname === '/create-invoice' \u0026\u0026 req.method === 'POST')\n    return booth.createInvoiceHandler(req)\n  return booth.middleware(req)\n})\n```\n\n### Hono\n\n```typescript\nimport { Hono } from 'hono'\nimport { createHonoTollBooth, type TollBoothEnv } from '@forgesworn/toll-booth/hono'\nimport { phoenixdBackend } from '@forgesworn/toll-booth/backends/phoenixd'\nimport { createTollBooth } from '@forgesworn/toll-booth'\nimport { sqliteStorage } from '@forgesworn/toll-booth/storage/sqlite'\n\nconst storage = sqliteStorage({ path: './toll-booth.db' })\nconst engine = createTollBooth({\n  backend: phoenixdBackend({ url: 'http://localhost:9740', password: process.env.PHOENIXD_PASSWORD! }),\n  storage,\n  pricing: { '/api': 10 },\n  upstream: 'http://localhost:8080',\n  rootKey: process.env.ROOT_KEY!,\n})\n\nconst tollBooth = createHonoTollBooth({ engine })\nconst app = new Hono\u003cTollBoothEnv\u003e()\n\n// Mount payment routes\napp.route('/', tollBooth.createPaymentApp({\n  storage,\n  rootKey: process.env.ROOT_KEY!,\n  tiers: [],\n  defaultAmount: 1000,\n}))\n\n// Gate your API\napp.use('/api/*', tollBooth.authMiddleware)\napp.get('/api/resource', (c) =\u003e {\n  const balance = c.get('tollBoothCreditBalance')\n  return c.json({ message: 'Paid content', balance })\n})\n\nexport default app\n```\n\n### Cashu-only (no Lightning node)\n\n```typescript\nimport { Booth } from '@forgesworn/toll-booth'\n\nconst booth = new Booth({\n  adapter: 'web-standard',\n  redeemCashu: async (token, paymentHash) =\u003e {\n    // Verify and redeem the ecash token with your Cashu mint\n    // Return the amount redeemed in satoshis\n    return amountRedeemed\n  },\n  pricing: { '/api': 5 },\n  upstream: 'http://localhost:8080',\n})\n```\n\nNo Lightning node, no channels, no liquidity management. Ideal for serverless and edge deployments.\n\n### xcashu (Cashu ecash via NUT-24)\n\n```typescript\nimport { Booth } from '@forgesworn/toll-booth'\n\nconst booth = new Booth({\n  adapter: 'web-standard',\n  xcashu: {\n    mints: ['https://mint.minibits.cash'],\n    unit: 'sat',\n  },\n  pricing: { '/api': 10 },\n  upstream: 'http://localhost:3000',\n})\n```\n\nClients pay by sending `X-Cashu: cashuB...` tokens in the request header. Proofs are verified and swapped at the configured mint(s) using cashu-ts.\n\nUnlike the `redeemCashu` callback (which integrates Cashu into the L402 payment-and-redeem flow), `xcashu` is a self-contained payment rail: the client attaches a token directly to the API request and gets access in one step — no separate redeem endpoint required. Both rails can run simultaneously; the 402 challenge will include both `WWW-Authenticate` (L402) and `X-Cashu` headers.\n\n### IETF Payment (draft-ryan-httpauth-payment-01)\n\n```typescript\nconst booth = new Booth({\n  adapter: 'express',\n  backend: phoenixdBackend({ url: '...', password: '...' }),\n  ietfPayment: {\n    realm: 'api.example.com',\n    // hmacSecret auto-derived from rootKey if omitted\n  },\n  pricing: { '/api': 10 },\n  upstream: 'http://localhost:8080',\n})\n```\n\nImplements the [IETF Payment authentication scheme](https://datatracker.ietf.org/doc/draft-ryan-httpauth-payment/) - the emerging standard for HTTP payment authentication. Challenges are stateless (HMAC-SHA256 bound, no database lookup on verify), with JCS-encoded charge requests and timing-safe validation. The 402 response includes a `WWW-Authenticate: Payment` header alongside the L402 challenge, so clients can use whichever scheme they support.\n\n---\n\n## Guides\n\n- [Monetise any Express API in 60 seconds](docs/guides/express-quickstart.md)\n- [Monetise your Ollama endpoint](docs/guides/ollama-monetisation.md)\n- [Let AI agents pay for your API](docs/guides/ai-agent-payments.md)\n\n---\n\n## Case studies\n\n- [satgate: Pay-per-token AI inference](docs/case-studies/satgate.md)\n- [jokes.trotters.dev: From zero to production L402 API](docs/case-studies/jokes-trotters-dev.md)\n\n---\n\n## Lightning backends\n\n```typescript\nimport { phoenixdBackend } from '@forgesworn/toll-booth/backends/phoenixd'\nimport { lndBackend } from '@forgesworn/toll-booth/backends/lnd'\nimport { clnBackend } from '@forgesworn/toll-booth/backends/cln'\nimport { lnbitsBackend } from '@forgesworn/toll-booth/backends/lnbits'\nimport { nwcBackend } from '@forgesworn/toll-booth/backends/nwc'\n```\n\nEach backend implements the `LightningBackend` interface (`createInvoice` + `checkInvoice`).\n\n| Backend | Status | Notes |\n|---------|--------|-------|\n| Phoenixd | Stable | Simplest self-hosted option |\n| LND | Stable | Industry standard |\n| CLN | Stable | Core Lightning REST API |\n| LNbits | Stable | Any LNbits instance - self-hosted or hosted |\n| NWC | Stable | Any Nostr Wallet Connect wallet (Alby Hub, Mutiny, Umbrel, Phoenix, etc.) — E2E encrypted via NIP-44 |\n\n---\n\n## Why not Aperture?\n\n[Aperture](https://github.com/lightninglabs/aperture) is Lightning Labs' production L402 reverse proxy. It's battle-tested and feature-rich. Use it if you can.\n\n| | Aperture | toll-booth |\n|---|---|---|\n| **Language** | Go binary | TypeScript middleware |\n| **Deployment** | Standalone reverse proxy | Embeds in your app, or runs as a gateway in front of any HTTP service |\n| **Lightning node** | Requires LND | Phoenixd, LND, CLN, LNbits, NWC, or none (Cashu-only) |\n| **Payment rails** | Lightning only | Lightning, Cashu ecash, xcashu (NUT-24), x402 stablecoins, IETF Payment - simultaneously |\n| **IETF Payment** | No | Yes - [draft-ryan-httpauth-payment-01](https://datatracker.ietf.org/doc/draft-ryan-httpauth-payment/) with stateless HMAC challenges |\n| **x402 stablecoins** | No | Yes - USDC on Base, Polygon via pluggable facilitator |\n| **Cashu ecash** | No | Yes - redeemCashu callback + xcashu (NUT-24) direct-header rail |\n| **Credit system** | No | Pre-paid balance with volume discount tiers |\n| **Framework adapters** | N/A (standalone proxy) | Express, Web Standard (Deno/Bun/Workers), Hono |\n| **Serverless** | No - long-running process | Yes - Web Standard adapter runs on Cloudflare Workers, Deno, Bun |\n| **Configuration** | YAML file | Programmatic (code) |\n\nFor a detailed comparison with all alternatives, see [docs/comparison.md](docs/comparison.md).\n\n---\n\n## x402 stablecoin payments\n\n[x402](https://x402.org) is Coinbase's HTTP 402 payment protocol for on-chain stablecoins. toll-booth speaks it natively. A single deployment accepts Lightning **and** x402 stablecoins **and** Cashu — simultaneously. The seller doesn't care how they get paid. They just want paid.\n\n```typescript\nconst booth = new Booth({\n  adapter: 'express',\n  backend: phoenixdBackend({ url: '...', password: '...' }),\n  x402: {\n    receiverAddress: '0x1234...abcd',\n    network: 'base',\n    facilitator: myFacilitator, // verifies on-chain settlement\n  },\n  pricing: { '/api': { sats: 10, usd: 5 } }, // price in both currencies\n  upstream: 'http://localhost:8080',\n})\n```\n\nThe 402 challenge includes both `WWW-Authenticate` (L402) and `Payment-Required` (x402) headers. Clients choose their preferred rail. x402 payments settle into the same credit system as Lightning — volume discount tiers apply regardless of payment method.\n\nThe unique value of toll-booth isn't any single payment rail. It's the **middleware layer**: gating, credit accounting, free tiers, volume discounts, upstream proxying, and macaroon credentials — all framework-agnostic, all runtime-agnostic, all payment-rail agnostic. Payment protocols are pluggable rails. toll-booth is the booth.\n\n---\n\n## Using toll-booth with any API\n\ntoll-booth works as a **reverse proxy gateway**, so the upstream API can be written in any language - C#, Go, Python, Ruby, Java, or anything else that speaks HTTP. The upstream service doesn't need to know about L402 or Lightning; it just receives normal requests.\n\n```\nClient ---\u003e toll-booth (Node.js) ---\u003e Your API (any language)\n                |                          |\n          L402 payment gating        Plain HTTP requests\n          Macaroon verification      X-Credit-Balance header added\n```\n\nPoint `upstream` at your existing service:\n\n```typescript\nconst booth = new Booth({\n  adapter: 'express',\n  backend: phoenixdBackend({ url: '...', password: '...' }),\n  pricing: { '/api/search': 5, '/api/generate': 20 },\n  upstream: 'http://my-dotnet-api:5000',  // ASP.NET, FastAPI, Gin, Rails...\n})\n```\n\nDeploy toll-booth as a sidecar (Docker Compose, Kubernetes) or as a standalone gateway in front of multiple services. See [`examples/valhalla-proxy/`](examples/valhalla-proxy/) for a complete Docker Compose reference - the Valhalla routing engine it gates is a C++ service.\n\n---\n\n## Production checklist\n\n- **Set a persistent `rootKey`** (64 hex chars / 32 bytes). Without it, a random key is generated per restart and all existing macaroons become invalid. Generate one with: `node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"`\n- Use a persistent `dbPath` (default: `./toll-booth.db`).\n- Enable `strictPricing: true` to prevent unpriced routes from bypassing billing.\n- Ensure your `pricing` keys match the paths the middleware actually sees (after mounting).\n- Set `trustProxy: true` when behind a reverse proxy, or provide a `getClientIp` callback for per-client free-tier isolation.\n- If you implement `redeemCashu`, make it idempotent for the same `paymentHash` - crash recovery depends on it.\n- Rate-limit `/create-invoice` at your reverse proxy - each call creates a real Lightning invoice.\n\n---\n\n## Example deployments\n\n### sats-for-laughs - build your own paid API\n\n[`examples/sats-for-laughs/`](examples/sats-for-laughs/) is the fastest path from \"I have an API\" to \"my API earns sats\". It's the same code that runs the [live demo](https://jokes.trotters.dev/). Includes a web frontend with QR codes and wallet adapter buttons, plus a JSON API for programmatic access. Clone it, change three env vars, deploy.\n\n```bash\ncd examples/sats-for-laughs\ncp .env.example .env          # add your Phoenixd credentials\ndocker compose up -d          # or: MOCK=true npm start\n```\n\nIncludes mock mode for local development (auto-settles invoices, no Lightning node needed), Docker Compose with Phoenixd, and a pre-generated pool of 100+ jokes across six topics.\n\n### valhalla-proxy - production reference\n\n[`examples/valhalla-proxy/`](examples/valhalla-proxy/) gates the [Valhalla](https://github.com/valhalla/valhalla) routing engine (a C++ service) behind Lightning payments. Full Docker Compose setup demonstrating toll-booth as a sidecar proxy in front of non-JavaScript infrastructure.\n\n---\n\n## Payment flow\n\n```mermaid\nsequenceDiagram\n    participant C as Client\n    participant T as toll-booth\n    participant U as Upstream API\n\n    C-\u003e\u003eT: GET /api/resource\n    T-\u003e\u003eT: Check free tier\n    alt Free tier available\n        T-\u003e\u003eU: Proxy request\n        U--\u003e\u003eT: Response\n        T--\u003e\u003eC: 200 + X-Free-Remaining header\n    else Free tier exhausted\n        T--\u003e\u003eC: 402 + Invoice + Macaroon\n        C-\u003e\u003eC: Pay via Lightning / Cashu / NWC\n        C-\u003e\u003eT: GET /api/resource\u003cbr/\u003eAuthorization: L402 macaroon:preimage\n        T-\u003e\u003eT: Verify macaroon + preimage\n        T-\u003e\u003eT: Settle credit + debit cost\n        T-\u003e\u003eU: Proxy request\n        U--\u003e\u003eT: Response\n        T--\u003e\u003eC: 200 + X-Credit-Balance header\n    end\n```\n\n1. Client requests a priced endpoint without credentials\n2. Free tier checked - if allowance remains, request passes through\n3. If exhausted - **402** response with BOLT-11 invoice + macaroon\n4. Client pays via Lightning, NWC, or Cashu\n5. Client sends `Authorization: L402 \u003cmacaroon\u003e:\u003cpreimage\u003e`\n6. Macaroon verified, credit deducted, request proxied upstream\n\n---\n\n## Configuration\n\nThe five most common options:\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `adapter` | `'express' \\| 'web-standard' \\| 'hono'` | Framework integration to use |\n| `backend` | `LightningBackend` | Lightning node (optional if using Cashu-only) |\n| `pricing` | `Record\u003cstring, number\u003e` | Route pattern to cost in sats |\n| `upstream` | `string` | URL to proxy authorised requests to |\n| `freeTier` | `{ requestsPerDay: number }` or `{ creditsPerDay: number }` | Daily free allowance per IP (request-count or sats-budget) |\n\nSee [docs/configuration.md](docs/configuration.md) for the full reference including `rootKey`, `creditTiers`, `trustProxy`, `nwcPayInvoice`, `redeemCashu`, and all other options.\n\n---\n\n## Documentation\n\n| Document | Description |\n|----------|-------------|\n| **[Why L402?](docs/vision.md)** | The case for permissionless, machine-to-machine payments |\n| **[Architecture](docs/architecture.md)** | How toll-booth, satgate, and 402-mcp fit together |\n| **[Configuration](docs/configuration.md)** | Full reference for all Booth options |\n| **[Deployment](docs/deployment.md)** | Docker, nginx, Cloudflare Workers, Deno, Bun, Hono |\n| **[Security](docs/security.md)** | Threat model, macaroon security, hardening measures |\n| **[Migration](docs/migration.md)** | Upgrading from v1 to v2, and v2 to v3 |\n| **[Contributing](CONTRIBUTING.md)** | Development setup, conventions, adding backends |\n\n---\n\n## Ecosystem\n\n| Project | Role |\n|---------|------|\n| **[toll-booth](https://github.com/forgesworn/toll-booth)** | **Payment-rail agnostic HTTP 402 middleware** |\n| [satgate](https://github.com/TheCryptoDonkey/satgate) | Production showcase — pay-per-token AI inference proxy (~400 lines on toll-booth) |\n| [402-mcp](https://github.com/forgesworn/402-mcp) | Client side — AI agents discover, pay, and consume L402 APIs |\n\n---\n\n## Support\n\nIf you find toll-booth useful, consider sending a tip:\n\n- **Lightning:** `thedonkey@strike.me`\n- **Nostr zaps:** `npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2`\n\n## Licence\n\n[MIT](LICENSE)\n","funding_links":["https://github.com/forgesworn/toll-booth#support"],"categories":["Tools"],"sub_categories":["Client reviews and/or comparisons"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforgesworn%2Ftoll-booth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fforgesworn%2Ftoll-booth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforgesworn%2Ftoll-booth/lists"}