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 orderapp.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

  1. Default to reusable — most addresses should accept multiple payments
  2. Use disposable for orders — prevent accidental double-payments
  3. Check state before generating — reject callbacks for already-paid disposables
  4. Clear UI indication — show users whether a link is one-time
  5. Handle edge cases — what if someone tries to pay a disposed link?