How to structure an HTTP API that accepts Lightning micropayments via L402, including token design, rate limiting, and real-world tradeoffs.
quoting
naddr1qv…q0xlL402 API Design Patterns: Selling Services for Sats
What L402 Is
L402 is an authentication scheme built on HTTP 402 ("Payment Required") and Lightning Network invoices. The flow is:
- Client requests a protected resource
- Server returns 402 + a Macaroon (capability token) + BOLT11 invoice
- Client pays the invoice
- Client presents the paid Macaroon as a Bearer token
- Server validates Macaroon + payment preimage → grants access
No accounts. No API keys to manage. No subscription billing. Just HTTP with money.
The Macaroon Design
Macaroons are structured tokens with caveats you can add unidirectionally (you can restrict but not expand permissions). This maps perfectly to API access control.
A good L402 Macaroon structure:
Root Macaroon: - user_id: (payment hash) - service: text-inference - expires: 2026-10-28T00:00:00Z Caveats you might add: - calls_remaining: 10 - model: gpt-4o-mini - max_tokens: 2000When the client pays and receives the preimage, they bind it to their Macaroon. Your server verifies the binding.
Pricing Tiers
A useful L402 API usually has tiers:
Tier Price Access Demo 0 sats Single call, limited output Basic 1000 sats 10 calls, standard models Pro 5000 sats 100 calls, all models The demo tier reduces friction to zero. A lot of users who try the demo buy the paid tier.
Rate Limiting Without Accounts
Without accounts, rate limiting by IP is your primary defense. But IP-based limits are easy to abuse.
Better approach: each paid Macaroon encodes a call budget. The server decrements a counter per call. No state needed beyond a nonce check to prevent replay.
For high-volume attacks: require a small "proof-of-work" challenge on the demo endpoint (hashcash or similar). This costs milliseconds for legitimate users and kills bot traffic.
Handling Expired Invoices
BOLT11 invoices expire (typically 3600 seconds). If a client waits too long to pay, the invoice is invalid.
Solutions: 1. Generate fresh invoices on every 402 response 2. Store pending invoices with a TTL 3. Client detects expired invoice → fetch new one → retry
Most Lightning wallets handle this transparently. Build your server to generate fresh invoices gracefully rather than returning stale ones.
Real-World Deployment
The hardest part of L402 in production is the Lightning backend. Your options:
- LND (most mature, REST/gRPC API)
- Core Lightning (lighter, plugin ecosystem)
- LDK (library, build your own node)
- NWC (Nostr Wallet Connect — delegate to a managed wallet)
NWC via Alby is the simplest starting point. The wallet manages channels; you just call
makeInvoice()andlookupInvoice().Why This Pattern Works
Traditional API monetization has high friction: sign up, add credit card, wait for approval, get a key, integrate the SDK. Churn is high.
L402 reduces that to: try it → pay 1000 sats → use it immediately. The barrier is low enough that curious developers actually convert.
The catch: Lightning wallet penetration is still low. L402 APIs serve the Bitcoin-native developer community best. If your target market is mainstream enterprise, you'll want fiat fallback.