ngx-l402
⚡ Our GitHub org is temporarily hidden by a false-positive flag (appeal pending). Code & releases are mirrored on Nostr — browse the repo ↗

Accept Lightning payments in front of any HTTP API.

ngx-l402 is an open-source nginx module that puts a Lightning paywall in front of any upstream — no API rewrite, no SDK. It speaks L402 (bLIP-26), settles over the Lightning Network you already run, and verifies every payment locally — you keep custody of the sats.

MIT-licensed· One nginx directive· 8 payment backends· Non-custodial
/etc/nginx/conf.d/api.conf
# Put L402 in front of any upstream — no API rewrite.
server {
  listen 443 ssl;
  server_name api.example.com;

  location /v1/ {
    l402                      on;
    l402_amount_msat_default  1000000;   # 1,000 sats / request
    l402_auto_detect_payment  on;        # client needn't echo the preimage

    proxy_pass                http://upstream;
  }
}
# Backend node — LND · CLN · LNURL · NWC · BOLT12 · Eclair — is set via env vars.

↑ that block turns api.example.com/v1/ into a 1,000-sat-per-request Lightning endpoint. No application code touched.

How it works, in three requests.

L402 pairs two things: a macaroon (the access token) and a Lightning preimage (proof of payment), tied together by sha256(preimage) == paymentHash. The client — a human, a script, an AI agent — hits the endpoint, pays the returned invoice on Lightning, and retries with the macaroon and preimage. No accounts, no API keys, no chain confirmations.

$ first request 01 · challenge
# Hit the protected endpoint with no auth.
$ curl -i https://api.example.com/v1/chat

HTTP/1.1 402 Payment Required
WWW-Authenticate: L402 macaroon="AGIAJEemVQUTEyNCR0exk7ek...",
                  invoice="lnbc10u1p3xnhl2pp5..."
Content-Type: application/json
Cache-Control: no-store

{
  "amount_sat": 1000,
  "description": "chat.completions · 1 request",
  "expires_at": "2026-05-21T18:04:11Z"
}
$ pay invoice 02 · settle
# Agent pays the invoice on its Lightning node.
# Any of 8 backends works — LND shown here.
$ lncli payinvoice \
  --pay_req=lnbc10u1p3xnhl2pp5...

Payment hash: 9c3a2b1f8e7d6c5b4a39281706f5e4d3...
Payment preimage: 7a3c91b4e2d058...
Status: SUCCEEDED  ·  Fee: 0 sat  ·  Hops: 3

# sha256(preimage) == paymentHash → proof of payment.
# Verified locally at the edge — no callback to any third party.
$ retry with preimage 03 · 200 OK
# Repeat the request with macaroon + preimage.
$ curl -H "Authorization: L402 \
    $MACAROON:$PREIMAGE" \
  https://api.example.com/v1/chat

HTTP/1.1 200 OK
Content-Type: application/json
X-L402-Settled: "1000 sat"

{
  "reply": "hello, agent.",
  "model": "gpt-4o-mini",
  "tokens": 42
}
// settles over Lightning · no chain confirmations, no accounts

Three reasons it lives at the edge.

01 · drop-in

One nginx directive, no API rewrite.

Two lines in your existing nginx config wrap any upstream — no SDK to import, no application code to touch, no language lock-in. The diff fits in one commit, and reviews like any other reverse-proxy change.

02 · multi-backend

Eight payment backends, one config line.

Point ngx-l402 at the Lightning infrastructure you already run. Swap backends without touching your application — change one directive and reload nginx.

LND
CLN
Eclair
LNC
LNURL
NWC
BOLT12
Cashu
03 · non-custodial

Self-hosted, and you keep the sats.

Payments settle straight to the Lightning node or address you control — no middleman, no platform cut, no third party holding funds or metering your traffic. Every preimage is verified locally at the edge, so nothing about a request leaves your box.

L402 bLIP-26 · live Cashu private ecash

Self-describing APIs for autonomous agents.

ngx-l402 publishes a /.well-known/l402-services manifest — the agent-era equivalent of robots.txt. A client given only a hostname can discover which routes are paid, what they cost, and which payment backends are accepted, then pay and proceed — no API keys, no out-of-band integration, no human in the loop.

$ curl https://api.example.com/.well-known/l402-services
{
  "version": "1",
  "service": { "name": "Example API", "description": "Stock data, paid per request." },
  "payment_methods": [
    { "type": "lightning", "backend": "LNURL", "address": "api@getalby.com" },
    { "type": "cashu", "mints": ["https://mint.minibits.cash"], "p2pk_supported": true }
  ],
  "routes": [
    { "path": "/v1/chat", "price": { "type": "static", "amount_msat": 1000000 } }
  ]
}

↑ one directive — l402_manifest; — makes the whole API surface discoverable: routes, prices, and payment backends, in one JSON document.

Run the gateway in 60 seconds.

Pre-built container, one docker command. The simplest setup needs only an LNURL address to receive at — no node to run. Swap in LND, CLN, NWC, BOLT12, or Eclair by changing env vars.

$ run the gateway
$ docker run -d --name l402-nginx -p 8000:8000 \
  -e LN_CLIENT_TYPE=LNURL \
  -e LNURL_ADDRESS=you@your-lnurl-server.com \
  -e ROOT_KEY=$(openssl rand -hex 32) \
  ghcr.io/dhananjaypurohit/ngx_l402:latest

# Gateway listens on :8000.
# curl http://localhost:8000/             → 200 OK
# curl -i http://localhost:8000/protected → 402 Payment Required
  1. 01

    Configure & run

    The image ships nginx, the module, and clients for all backends, with a default /protected route. Mount your own nginx.conf to define paid locations.

  2. 02

    Hit the endpoint

    Any request to a protected route comes back as 402 Payment Required with a Lightning invoice in the WWW-Authenticate header.

  3. 03

    Pay → preimage → 200

    Pay the invoice from any Lightning wallet. Retry with the preimage in the Authorization header — the gateway verifies locally and proxies through to your upstream.

How it compares.

L402 has a healthy ecosystem — but most tools live at a different layer: hosted platforms, or client libraries that pay endpoints. ngx-l402 is the self-hosted, non-custodial server side, as a module in the nginx you already run.

  ngx-l402 Aperture Fewsats Lightning Enable
What it is nginx module standalone proxy hosted platform hosted layer
Self-hosted yes — your nginx yes (+ etcd) no no (BYO key)
Non-custodial yes yes platform-mediated custodial provider
Backends LND · LNC · CLN · Eclair · LNURL · NWC · BOLT12 · Cashu LND abstracted Strike / OpenNode
Node required no (LNURL / NWC) yes (LND) no no
Cashu / privacy yes no no no
Discovery manifest /.well-known/l402-services no index no

// client libs — Alby fetchWithL402, l402-requests, Fewsats SDK — pay ngx-l402 endpoints. Allies, not alternatives.

Built to sit in front of money.

non-custodial

Funds never touch the gateway.

Payments settle straight to your Lightning node or address. The module only verifies proof of payment — sha256(preimage) == paymentHash — locally at the edge. Nothing about a request leaves your box.

replay-safe

Spent proofs can't be reused.

Macaroons are bound to the exact request path, and settled preimages and Cashu tokens are recorded in Redis. If Redis is configured but unreachable, the gateway fails closed — it returns 402 rather than letting a request through unpaid.

abuse-resistant

Rate-limit invoice spam.

Per-route, per-IP invoice rate limiting (l402_invoice_rate_limit) caps challenge generation. Cashu P2PK proofs verify offline (NUT-12 DLEQ) — no mint round-trip on the hot path.

Questions.

Do I need to run a Lightning node?

No. The LNURL and NWC backends let you point at any Lightning address or wallet — no node to operate. Run LND / CLN / Eclair only if you want to.

Is it custodial? Where do the sats go?

Non-custodial. Payments go directly to the node or address you configure. ngx-l402 never holds funds — it only checks proof of payment.

What happens if a payment fails or is never made?

The client simply doesn't get access — it keeps receiving 402. No settlement, no access, and nothing to refund, because the gateway never takes custody.

Can someone spam my endpoint with invoice requests?

Set l402_invoice_rate_limit per route to cap invoice generation per IP. With Redis configured, replay and spend-tracking are shared across all nginx workers.

How does an AI agent know what a route costs?

Enable l402_manifest to publish /.well-known/l402-services — a JSON document of routes, prices, and accepted backends. An agent with only your hostname can discover and pay.

Which wallets work?

Any wallet that returns a standard 32-byte preimage. Note: some wallets (e.g. Wallet of Satoshi) return a non-standard 48-byte preimage that won't work with the classic flow — use auto-detect mode or a standard wallet.

Can I test it without spending real sats?

Yes — point it at a regtest LND or a Cashu mint for local testing. Mainnet and regtest use the same config.