What it is
+
+ A Lightning Address looks like an email address and works like one for payments: anyone
+ can type bob@example.com into a wallet and pay you, without QR codes,
+ invoices pasted over chat, or a new invoice for every payment.
+
+ Under the hood it is just HTTPS. A wallet turns bob@example.com into a
+ request to https://example.com/.well-known/lnurlp/bob, reads the payment
+ parameters it gets back, and then asks the same server for a bolt11 invoice of the
+ amount the payer chose. This library is that server, written in PHP.
+
Spec-compliant
+Implements LNURL-pay (LUD-06), the format wallets already speak.
+Multi-user
+One deployment serves many usernames, each mapped to its own invoice backend and API key.
+Backend-agnostic
+LNbits ships today; other backends are an interface implementation away.
+Small and typed
+PHP 8.3+, Psalm errorLevel 1 and PHPStan level max on every commit.
+Why self-host it
+-
+
- The address is yours. It lives on your domain, so no provider can rename, rate-limit, or discontinue it. +
- Payments go straight to your node. The library asks your wallet backend for invoices; nobody custodies the sats on the way. +
- No new stack. If you already run PHP, this is one route and two config files, not another service to operate. +
- Multiple identities, one deployment. Family, team, or project addresses can share a domain while each keeps its own backend. +
How it works
+-
+
-
+
The wallet resolves the address
++
+bob@example.combecomes +GET https://example.com/.well-known/lnurlp/bob, which your server + routes to this library. +
+ -
+
The server returns pay parameters
+Minimum and maximum sendable amounts, a description, and the callback URL to use next.
+
+{ + "callback": "https://example.com", + "maxSendable": 10000000000, + "minSendable": 100000, + "metadata": "[[\"text/plain\",\"Pay to bob@example.com\"], …]", + "tag": "payRequest", + "commentAllowed": false +}
+ -
+
The wallet asks for an invoice
++ It calls the callback with the chosen amount in millisatoshis: +
+GET /bob?amount=2000. +
+ -
+
Your node issues it
++ The library requests a bolt11 invoice from the backend configured for that + username and hands it back under
+pr. The wallet pays it directly — + the library never touches funds. +
+{ + "pr": "lnbc20n1p…", + "status": "OK", + "successAction": { "tag": "message", "message": "Payment received!" }, + "routes": [], + "disposable": false, + "error": null +}
+
Quick start
+Requires PHP 8.3 or newer and an LNbits wallet API key.
+composer require php-lightning/lnaddress
+
+cp lightning-config.dist.php lightning-config.php # settings
+cp backends.dist.json backends.json # per-user invoice backends
+
+composer serve # http://localhost:8080
+
+ Settings — lightning-config.php
+use PhpLightning\Config\LightningConfig;
+
+return (new LightningConfig())
+ ->setDomain('yourdomain.com')
+ ->setReceiver('default-receiver')
+ ->setDescriptionTemplate('Pay to %s') // %s = the lightning address
+ ->setSuccessMessage('Thanks for the payment!')
+ ->setSendableRange(min: 100_000, max: 10_000_000_000) // millisats
+ ->setCallbackUrl('https://yourdomain.com')
+ ->addBackendsFile(getcwd() . '/backends.json');
+
+ Backends — backends.json
+{
+ "bob": { "type": "lnbits", "api_key": "abc...123", "api_endpoint": "http://localhost:5000" },
+ "alice": { "type": "lnbits", "api_key": "def...456", "api_endpoint": "http://localhost:5000" }
+}
+
+
+ Then point /.well-known/lnurlp/{username} at the app's
+ /{username} route, over HTTPS, and the address is live.
+
Under the hood
++ Built on the Gacela framework as a small + modular monolith. Each module exposes a facade; the domain layer is pure PHP that + knows nothing about HTTP, which is what makes swapping the invoice backend — or + calling the library directly instead of over HTTP — straightforward. +
+public/index.php → Router → InvoiceRoutesPlugin
+ ├─ CorsMiddleware wallets call cross-origin
+ ├─ InvoiceExceptionHandler errors → LNURL error object
+ └─ InvoiceController
+ └─ InvoiceFacade
+ ├─ CallbackUrl → pay parameters
+ └─ InvoiceGenerator → bolt11 invoice
+ └─ LnbitsBackendInvoice → your node
+
+ Using it as a library, without the HTTP layer:
+use Gacela\Framework\Gacela;
+use PhpLightning\Invoice\InvoiceFacade;
+
+Gacela::bootstrap(__DIR__);
+
+$facade = new InvoiceFacade();
+$payParams = $facade->getCallbackUrl('bob');
+$invoice = $facade->generateInvoice('bob', 2_000); // millisats
+
+ Documentation
+| Guide | Contents |
|---|---|
| Getting started | +Install, configure, run locally, deploy behind a real domain | +
| Configuration | +Every setter, its default, and the backends file format | +
| HTTP API | +Routes, payloads, CORS, LNURL error objects, nginx rewrite | +
| Architecture | +Modules, layers, request flow, adding a backend | +
| Development | +Composer scripts, tests, static analysis, releasing | +
FAQ
+ +Does the library hold my money?
++ No. It asks your configured backend for an invoice and returns it. The payment settles + between the payer's wallet and your node. +
+ +Do I need LNbits?
+
+ LNbits is the backend implemented today. Adding another means a case in the
+ BackendType enum and an implementation of
+ BackendInvoiceInterface — the rest of the code depends on the interface,
+ not on LNbits.
+
Can one domain serve several people?
+
+ Yes. Every entry in backends.json is a username with its own endpoint and
+ API key, so bob@ and alice@ can point at different wallets.
+
Does it work in a browser wallet?
+
+ Yes — every response carries permissive CORS headers and OPTIONS
+ preflights are answered directly.
+
What is not implemented?
+
+ LNURL comments (LUD-12): commentAllowed is always false.
+