CapabilitiesReusable Requests
Reusable Requests (LUD-11)
Reusable Requests define whether a payment link is persistent (can be used multiple times) or disposable (single-use).
Why It Matters
Different payment scenarios need different behaviors:
- Tip jars — reusable, accept unlimited payments
- Invoice links — disposable, one payment only
- Donation pages — reusable with tracking
- One-time purchases — disposable to prevent double-payment
How It Works
Server Response
Include disposable in your LNURL response:
{
"callback": "https://domain.com/lnurlp/user/callback",
"tag": "payRequest",
"minSendable": 1000,
"maxSendable": 100000000000,
"metadata": "[[\"text/plain\",\"Pay user\"]]",
"disposable": false
}
Behavior
| disposable | Behavior |
|--------------|----------|
| false (or omitted) | Can be paid multiple times |
| true | Should only be used once |
Use Cases
Persistent Address (Default)
Your Lightning Address is reusable by default:
{
"callback": "https://zbd.gg/lnurlp/andre/callback",
"disposable": false,
"metadata": "[[\"text/plain\",\"Pay andre@zbd.gg\"]]"
}
Anyone can pay andre@zbd.gg as many times as they want.
One-Time Payment Link
For a specific invoice or purchase:
{
"callback": "https://shop.example/pay/order-12345/callback",
"disposable": true,
"minSendable": 100000000,
"maxSendable": 100000000,
"metadata": "[[\"text/plain\",\"Order #12345 - Widget Pro\"]]"
}
This link represents a specific $10 order and should only be paid once.
Implementation Example
// Generate a one-time payment link for an order
app.get('/pay/:orderId', async (req, res) => {
const order = await getOrder(req.params.orderId);
if (!order) {
return res.status(404).json({ status: 'ERROR', reason: 'Order not found' });
}
if (order.paid) {
return res.status(400).json({ status: 'ERROR', reason: 'Already paid' });
}
res.json({
callback: `https://shop.example/pay/${order.id}/callback`,
tag: 'payRequest',
minSendable: order.amountMsats,
maxSendable: order.amountMsats,
metadata: JSON.stringify([['text/plain', `Order #${order.id}`]]),
disposable: true
});
});
app.get('/pay/:orderId/callback', async (req, res) => {
const order = await getOrder(req.params.orderId);
if (order.paid) {
return res.status(400).json({
status: 'ERROR',
reason: 'Order already paid'
});
}
const invoice = await generateInvoice({
amount: order.amountMsats,
description: `Order #${order.id}`
});
// Mark as pending payment
await markOrderPending(order.id, invoice.paymentHash);
res.json({ pr: invoice.bolt11 });
});
Best Practices
- Default to reusable — most addresses should accept multiple payments
- Use disposable for orders — prevent accidental double-payments
- Check state before generating — reject callbacks for already-paid disposables
- Clear UI indication — show users whether a link is one-time
- Handle edge cases — what if someone tries to pay a disposed link?