lightning address
2025-08-25 ยท 3 min read
A lightning address is an email-like string (ex: "philip@lexe.app") that lets users pay to a static human-readable identifier instead of a raw, single-use BOLT11 invoice.
- LUD-16 (Lightning Address): https://github.com/lnurl/luds/blob/luds/16.md
- LUD-06 (payRequest): https://github.com/lnurl/luds/blob/luds/06.md
- LUD-09 (payRequest successAction): https://github.com/lnurl/luds/blob/luds/09.md
- LUD-10 (payRequest successAction aes): https://github.com/lnurl/luds/blob/luds/10.md
- LUD-11 (payRequest disposable): https://github.com/lnurl/luds/blob/luds/11.md
- LUD-12 (payRequest commentAllowed): https://github.com/lnurl/luds/blob/luds/12.md
- LUD-18 (payRequest payerData): https://github.com/lnurl/luds/blob/luds/18.md
- LUD-20 (payRequest long description): https://github.com/lnurl/luds/blob/luds/20.md
- LUD-21 (payRequest verify paid endpoint): https://github.com/lnurl/luds/blob/luds/21.md
payment flow #
- Payer wants to pay "philip@lexe.app".
- Payer client makes a request
GET https://lexe.app/.well-known/lnurlp/philip
. - Server responds with an LNURLp JSON blob.
{
"callback": "<callback>",
"minSendable": 1,
"maxSendable": 100000000,
"metadata": "<metadata>",
"commentAllowed": 200,
"tag": "payRequest"
}
or
{
"status": "ERROR",
"reason": "No such user"
}
- Payer client follows LUD-06 (payRequest) flow.
- Payer client makes a request
GET <callback><?|&>amount=<msats>
. - Server responds with a JSON blob:
{
"pr": "<bolt11-invoice>",
"routes": []
}
or
{
"status": "ERROR",
"reason": "Amount is too small"
}
Payer client verifies the BOLT11 invoice:
- invoice
description_hash
in the BOLT11 invoice matchesSHA256(metadata)
. - invoice
amount
matches the requested amount (if any).
- invoice
Payer client pays the invoice.
details #
The
metadata
string must be hashed with SHA256 and included in the BOLT11 invoice'sdescription_hash
field.Endpoints needs proper CORS headers so browsers will use them.
design notes (Cloudflare) #
problem #
We currently serve https://lexe.app
from Cloudflare pages, but we need to somehow serve https://lexe.app/.well-known/lnurlp/*
from our backend gateway
service.
solution 1: redirect #
By far the simplest approach is to configure _redirects
in our Cloudflare pages repo to redirect requests to /.well-known/lnurlp/*
to our backend.
The main downside is that dumb clients that don't follow redirects will break.
solution 2: cloudflare workers reverse proxy #
Unfortunately, there's no origin rewrite rule available until Enterprise tier (>$2000/mo, "call me" pricing). The cloudflare-approved way to do this is to use a Cloudflare Worker function at that path that makes the request to our backend and returns the response.
Fortunately Worker CPU time limits don't apply to time spent waiting for fetch
requests.
Pricing: Free: 100k requests/day, 10ms CPU time/request
solution 3: serve LNURLp blobs from Azure blob storage / Cloudflare R2 #
Cloudflare does support serving static files from various object storage providers (Cloudflare R2, AWS S3, Google Cloud Storage, Azure Blob Storage). Because the LNURLp blobs contain a "callback" URL that can point to our actual backend, we can just serve static JSON files from blob storage.
problem #
Our callback
will have uncontrolled clients hitting our backend services, which means we need public webpki certs issued.