Self-host your own Lightning Address in PHP: a human-readable identifier like you@yourdomain.com that any Lightning wallet can pay. It implements LNURL-pay (LUD-06) and is backend-agnostic — LNbits is the backend available today. Built on the Gacela framework.
- PHP >= 8.2
composer require php-lightning/lnaddresscomposer install runs a post-install step that copies backends.dist.json → backends.json if the latter does not exist yet.
Prefer starting from a working project? Use the ready-made demo template. It depends on this library, so a composer update pulls in new features and fixes as they land here.
There are two config files: lightning-config.php (settings) and backends.json (per-user invoice backends).
cp lightning-config.dist.php lightning-config.phpLightningConfig has a fluent API:
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!')
->setInvoiceMemo('')
->setSendableRange(min: 100_000, max: 10_000_000_000) // millisats
->setCallbackUrl('https://yourdomain.com')
->addBackendsFile(getcwd() . '/backends.json');cp backends.dist.json backends.jsonEach username maps to its own invoice backend:
{
"bob": { "type": "lnbits", "api_key": "abc...123", "api_endpoint": "http://localhost:5000" },
"alice": { "type": "lnbits", "api_key": "def...456", "api_endpoint": "http://localhost:5000" }
}You can skip backends.json and register backends directly in lightning-config.php:
use PhpLightning\Config\Backend\LnBitsBackendConfig;
$config->addBackend('bob', LnBitsBackendConfig::withEndpointAndKey('http://localhost:5000', 'abc...123'));composer serveThis starts php -S localhost:8080 public/index.php.
One route serves the full LNURL-pay flow: GET /{username?}. The username is optional — when omitted, the request resolves to the default receiver@domain from your config.
Every response carries permissive CORS headers (Access-Control-Allow-Origin: *) so browser-based wallets can call it, and OPTIONS preflight requests are answered directly. Uncaught errors are turned into the LNURL error object by a global handler.
GET /bob (no amount) returns the LNURL-pay parameters:
{
"callback": "https://yourdomain.com",
"maxSendable": 10000000000,
"minSendable": 100000,
"metadata": "[[\"text/plain\",\"Pay to bob@yourdomain.com\"],[\"text/identifier\",\"bob@yourdomain.com\"]]",
"tag": "payRequest",
"commentAllowed": false
}GET /bob?amount=<millisats> returns a bolt11 invoice for that amount:
{
"pr": "lnbc20n1p...",
"status": "OK",
"memo": "",
"successAction": { "tag": "message", "message": "Thanks for the payment!" },
"routes": [],
"disposable": false,
"error": null
}Failures return an LNURL error object, for example when the amount falls outside the sendable range or the backend is unreachable:
{ "status": "ERROR", "reason": "Amount is not between minimum and maximum sendable amount" }Units: the sendable range and the
amountquery param are in millisats. The backend is billed in sats (millisats / 1000).
You can call the facade directly instead of going over HTTP:
use Gacela\Framework\Gacela;
use PhpLightning\Invoice\InvoiceFacade;
Gacela::bootstrap(__DIR__);
$facade = new InvoiceFacade();
$payParams = $facade->getCallbackUrl('bob'); // LNURL-pay params
$invoice = $facade->generateInvoice('bob', 2_000); // millisats| Setter | Purpose | Default |
|---|---|---|
setDomain(string) |
Your domain (URL scheme is stripped) | — |
setReceiver(string) |
Default username when none is in the URL | — |
setSendableRange(int $min, int $max) |
Allowed amounts, in millisats | 100_000 – 10_000_000_000 |
setCallbackUrl(string) |
Public callback base URL wallets call back to | — |
setDescriptionTemplate(string) |
LNURL metadata description (%s = the address) |
Pay to %s |
setSuccessMessage(string) |
Message shown after a successful payment | Payment received! |
setInvoiceMemo(string) |
Memo attached to the invoice | '' |
addBackendsFile(string $path) / addBackend(string $username, ...) |
Register invoice backends | — |
Backends are keyed by a type string, resolved through the PhpLightning\Config\Backend\BackendType enum. To add one:
- Add a case to
PhpLightning\Config\Backend\BackendType. - Handle that case in
LightningConfig::createBackendConfig(). - Implement
PhpLightning\Invoice\Domain\BackendInvoice\BackendInvoiceInterface.
composer test-all # quality + phpunit + rector (dry-run)Other useful scripts:
composer test-phpunit— run the PHPUnit suitecomposer quality— php-cs-fixer (dry-run), psalm, phpstancomposer fix— php-cs-fixer + rector (apply fixes)
See .github/CONTRIBUTING.md before opening a PR.
More details in the wiki.
Issues and pull requests are welcome. Licensed under MIT.