Lightning Setup
LNbits, node options, environment variables, webhooks and costs.
What to set up, how to set it up — from local dev to production.
Architecture choice: LNbits-first
ZapAPI ──(REST, X-Api-Key)──> LNbits ──(funding source)──> Lightning Node- LNbits = wallet account system + LNURL extension + multi-wallet isolation
- Funding source = the actual Lightning node holding liquidity (LND/CLN/Phoenixd/hosted)
1. Environments
| Env | LNbits | Node | Network |
|---|---|---|---|
| Local dev | Docker LNbits | Polar/regtest or Mutinynet | regtest/signet |
| Staging | staging.lnbits.zapapi.com | Mutinynet or small mainnet | signet/mainnet-small |
| Production | lnbits.zapapi.com (self-host, DO/AWS) | Voltage LND or self-host LND | mainnet |
Why Voltage for the node (research-backed)
- Managed LND in minutes, no blockchain sync wait, you keep keys, no KYC
- ~$30–100/mo depending on channel liquidity
- Alternative: self-host LND on a $20 VPS + ThunderHub for ops
2. Local Development Setup
# Option A — quickest: hosted LNbits demo (demo.lnbits.com) — OK for hacking, NOT prod
# Option B — Docker LNbits (recommended)
# Add to docker-compose.yml:
lnbits:
image: lnbits/lnbits:latest
ports: ["5000:5000"]
environment:
LNBITS_BACKEND_WALLET_CLASS: VoidWallet # fake wallet for pure API testing
LNBITS_DATA_FOLDER: ./data
LNBITS_ADMIN_USERS: "" # set your user id after first login
LNBITS_EXTENSIONS: "lnurlp" # enable LNURL extension
volumes: [lnbits-data:/data]Dev with fake payments: VoidWallet makes LNbits behave fully but settle instantly —
perfect for unit/e2e tests.
For real Lightning in dev: use Mutinynet (signet with LN) via hosted
lnbits.com or Alby test accounts.
3. Production LNbits Setup
# docker-compose.prod.yml (on a dedicated VPS, 2GB RAM min)
lnbits:
image: lnbits/lnbits:latest
restart: always
environment:
LNBITS_BACKEND_WALLET_CLASS: LndRestWallet
LND_REST_ENDPOINT: https://your-node:8080/
LND_REST_CERT: /lnd/tls.cert
LND_REST_MACAROON: /lnd/admin.macaroon
LNBITS_DATA_FOLDER: ./data
LNBITS_ADMIN_USERS: <comma-separated-admin-user-ids>
LNBITS_ALLOWED_USERS: <restrict account creation in prod>
LNBITS_EXTENSIONS: "lnurlp,withdraw" # + any others needed
LNBITS_SITE_TITLE: "ZapAPI Treasury"
LNBITS_DISABLED_EXTENSIONS: "admin-ng" # lock down what you don't use
volumes:
- lnbits-data:/data
-./lnd:/lnd:roHardening checklist
- [] Behind nginx/Caddy with TLS only
- [] Not publicly exposed for account creation (
LNBITS_ALLOWED_USERS) — ZapAPI server is the only creator of wallets (admin UI locked to our IPs) - [] Postgres backend for LNbits in prod (default SQLite won't scale)
LNBITS_DATABASE_URL=postgres://... - [] Daily backup of LNbits DB + macaroons in secrets manager
- [] Separate macaroon scopes: invoice-only for hot paths, admin for wallet creation
4. ZapAPI LNbits Integration (already built — config recap)
Each ZapAPI provider record stores encrypted:
{ "adminKey": "…", "invoiceKey": "…" } // per-user wallet keysinvoiceKeycreate/check invoices (used in hot path)adminKeypay invoices (withdrawals), create LNURL links- Encryption: AES-256-CBC with
ENCRYPTION_KEYenv (32-byte hex)
Per-user wallet provisioning (to implement in Phase 1)
on user signup:
1. POST /api/v1/account (LNbits superuser) create wallet "user_<id>"
2. store wallet_id + adminkey + inkey (encrypted) on user_wallet row
3. LightningAddress row created when user claims username5. Webhooks
Inbound (LNbits ZapAPI) IMPLEMENTED
POST /api/v1/webhooks/lnbits/:token? — receives settled-payment notifications.
Setup: in each LNbits wallet's settings, set the webhook URL:
https://api.zapapi.com/api/v1/webhooks/lnbits/<LNBITS_WEBHOOK_SECRET>Set LNBITS_WEBHOOK_SECRET in server env (random 32+ char string). In dev, leave
it unset to skip verification.
On each settled payment the handler fans out (failure-isolated):
- invoices
markAsPaid(project invoices from the /invoices API) - zaps settle + publish NIP-57 receipt instantly (30s cron remains as backstop)
- withdrawals reconcile outgoing payments (preimage/fee)
Idempotent: already-settled records are skipped.
Outbound (ZapAPI merchant)
- Merchant registers URL per project
- Every delivery signed:
X-ZapAPI-Signature: t=<unix>,v1=<hmac_sha256(secret, t + "." + rawBody)> - Retries: 5 attempts, exponential backoff (1m, 5m, 30m, 2h, 8h) via existing Bull queue
- Delivery log + replay button in dashboard (Phase 3)
6. Lightning Address / LNURL endpoints IMPLEMENTED
Status: built (Phase 1.1–1.3, 1.5). See server/src/lnurl/ and
client/app/.well-known/.
Serving topology (Vercel):
wallet https://zapapi.com/.well-known/lnurlp/alice (Next.js route handler)
http://api.zapapi.com/api/v1/lnurl/params/alice (NestJS, X-Served-Domain)Env vars:
# server
PRIMARY_DOMAIN=zapapi.com # default domain for addresses
SERVED_DOMAINS=zapapi.com,zapapi.io # multi-domain allowlist
PUBLIC_URL=https://zapapi.com # fallback callback base URL
LNURL_MIN_SENDABLE_MSAT=1000
LNURL_MAX_SENDABLE_MSAT=100000000000
LNURL_COMMENT_ALLOWED=280
NOSTR_RELAYS=wss://relay.damus.io,wss://nos.lol,wss://relay.primal.net
NOSTR_ZAP_SIGNING_KEY=<64-hex privkey> # platform key that signs zap receipts (NIP-57)
# client (Vercel)
API_URL=https://api.zapapi.com/api/v1 # server-side proxy target
MIGRATED_DOMAINS= # old domains 301 to PRIMARY_DOMAINEndpoints:
GET /.well-known/lnurlp/:username LUD-06 params (+allowsNostr/nostrPubkey for zaps)
GET /lnurl/callback/:username fresh BOLT11 (description_hash for NIP-57 zaps)
GET /.well-known/nostr.json?name= NIP-05 {names, relays}
GET /api/v1/lnurl/availability/:u dashboard username checkNote: LNbits lnurlp extension can also serve these, but we implement them in
ZapAPI so we control zap validation, CRM capture, and analytics — LNbits stays a pure
wallet backend.
7. Liquidity & Ops
| Concern | Approach |
|---|---|
| Inbound liquidity | Open channels to well-connected nodes (ACINQ, Kraken, Wallet of Satoshi) or buy inbound via Lightning Pool/Magma |
| Monitoring | Node balance alerts, LNbits uptime check, invoice failure rate |
| Backups | LNbits DB daily, LND SCB (static channel backup) after every channel change |
| Custody ceiling | Alert when total user balances > threshold; encourage auto-sweep |
8. Costs (rough monthly, production)
| Item | Cost |
|---|---|
| VPS for LNbits (4GB) | $20–40 |
| Voltage LND node | $30–100 + channel capital |
| Postgres (existing) | $0 (shared) |
| Channel liquidity (one-time capital) | 0.05–0.5 BTC depending on volume |
| Total fixed | ~$50–150/mo to start |