ZapAPI Docs
Get StartedConceptsAI GatewayGuidesPayment GatewaySDKAPI ReferenceResources
Payment Gateway

Security & Compliance

Key management, custody controls and regulatory posture.

1. Secret inventory

SecretStorageRotation
LNbits user wallet keysAES-256-CBC in Postgres (existing pattern), key from ENCRYPTION_KEY envon compromise; document procedure
Platform LNbits superuserenv / secrets manager (never DB)quarterly
Webhook signing secretsper-project, hashed display, full value shown once at creationuser-initiated
API keys (zap_live_)store SHA-256 hash only (like Stripe), prefix for lookupuser-initiated
Platform Nostr zap-signing nsecKMS/HSM in prod, env in devquarterly
User Nostr nsecNEVER stored — client-side only (NIP-07/46)n/a
DB backupsencrypted at rest, 30-day retentionn/a

Rules:

  • No secret ever in logs (existing sanitization covers passwords/tokens — extend to X-Api-Key, preimage is fine to show to invoice owner only)
  • ENCRYPTION_KEY rotation = re-encrypt script, tested in staging

2. Custody model & risk controls

We hold user funds (LNbits sub-wallets) and act as a responsible custodian:

  1. Withdrawals from day 1 — no lock-in, no minimum (minus LN fee reserve)
  2. Auto-sweep (Phase 4): user sets threshold auto-withdraw to their address
  3. Custody ceiling alert: total platform liability > X BTC page on-call
  4. Withdrawal safety:
  • TOTP 2FA required to enable/disable withdrawals address book
  • New withdrawal address 24h hold (opt-out) + email confirmation
  • Velocity limits (e.g. ≤ 3 withdrawals/hour, configurable)
  1. Hot wallet minimization: keep only operating float on LNbits node; treasury in cold storage; documented top-up procedure
  2. Segregation: one LNbits wallet per user (done) — a breach of one wallet key ≠ all funds

3. Application security

Existing: JWT+refresh, RBAC, rate limiting, XSS/SQLi sanitization, Winston logs, Sentry.

Add for payment surface:

  • LNURL endpoints: aggressive per-IP rate limit + Cloudflare/edge cache of params
  • Invoice creation: per-key daily volume caps (kill compromised keys' blast radius)
  • Webhook endpoints: replay protection (timestamp window) + signature (done in spec)
  • NIP-57 validation strictness (sig, amount match, time window) — zap spam vector
  • Admin actions (fee change, manual credit) audit log table w/ actor + diff
  • Subdomain take-over care on custom domains feature
  • Dependency scanning (existing CI) + quarterly penetration test before fiat-scale

4. Regulatory Posture (NOT legal advice — engage counsel)

QuestionPragmatic stance
Are we a money transmitter?Custodial likely MSB obligations in US/EU depending on volume/jurisdiction. Options: (a) register/get licenses, (b) partner with licensed custodian, (c) pivot custody to user-owned NWC wallets for large balances (hybrid). Decide before public launch with counsel.
KYC/AMLTiered: no-KYC under low limits (tips/zaps typical), KYC above thresholds & for fiat conversions. Keep per-user lifetime volume tracking ready.
SanctionsBlocklist screening on withdrawal destinations is impractical on LN; mitigate via limits + monitoring + ToS
TaxesWe're a service provider; creators get CSV exports for their own reporting (1099-K equivalents N/A for BTC in most places — document)
GDPRCustomer CRM = personal data (npub/email). Provide export + delete endpoints (feature 7.8). Data processing agreement for EU creators.
Terms of ServiceMust cover: custody risk, no chargebacks, prohibited use, fee schedule, termination. Template from BTCPay/OpenNode ToS as reference.

5. Incident Response

  1. Compromised LNbits freeze withdrawals endpoint (feature flag), rotate all keys via script, notify users (email + status page)
  2. Compromised API key user revokes in dashboard; we offer forced-rotate per project
  3. Zap receipt relay failure non-critical (funds settled); retry queue 24h
  4. Double-credit bug ledger is append-only; corrections as reversing entries, never UPDATE balance fields directly
  5. Status page (status.zapapi.com) + postmortem template in repo

6. Data Integrity (money math)

  • All amounts: integer msats in DB (bigint), never float
  • Ledger pattern: wallet_transactions append-only with type, amount, ref_id; cached balance column updated in same DB transaction
  • Nightly reconciliation job: sum(ledger) vs wallet.balance vs LNbits balance alert on drift > 1000 sats

On this page