Operational Checklist
Run through this before flipping your integration on for production users.
LNURL-pay endpoint
-
/.well-known/lnurlp/<user>returnscommentAllowed >= 256,minSendable <= 1000, and a callback URL. - Callback handler returns an invoice whose msat amount exactly matches the
?amount=parameter (use msat-precision fields like LNDValueMsat/ CLNamount_msat/ EclairamountMsat, not the rounded-sat variants). - Description hash on the invoice matches sha256 of the metadata string from the lnurlp endpoint (LUD-06 compliance, most LNURL-pay parsers verify this).
- Your endpoint is not rate-limiting the Satogram operator’s IPs aggressively. The service can hit
/.well-known/lnurlp/<user>in tight succession during a campaign. Skipped users get no payment.
Invoice subscriber
- Your invoice subscriber persists a resumable cursor and resumes on restart (LND:
add_index; CLN:lastpay_index; LDK: persisted event handler; Eclair: WebSocket reconnect plus agetreceivedinfosweep of the recent window). - Your subscriber reads TLV
6789998212and splits on,so it handles both single-address (Path A) and batch (Path B) payments. - Your subscriber tolerates unknown recipients in a CSV (a typo, deleted user, etc.) without throwing.
- Credit writes are idempotent on
(payment_hash, recipient)so a subscription replay after restart doesn’t double-credit.
User experience
- You have a path to display the message (TLV
34349334) to the user in your app: notification copy, transaction history, in-app inbox. - The message field is treated as untrusted UTF-8: escape for HTML rendering, cap displayed length, consider stripping unsafe Unicode (RTL overrides, zero-width chars). There is no verified sender, text inside the message that reads
from alice@example.comis just text the sender typed. - Your accounting layer is happy with credits as small as 1 sat (Path A), or you have a documented dust threshold (Path B).
Testing
- You have run the end-to-end regtest recipe in Testing and seen both Path A and Path B credit your test user.
- You have run the negative tests (no TLVs, unknown user, mixed batch) and verified your code’s behavior matches your written policy.
- You have a test recipient in production that you can pay end-to-end to verify the live flow. From an LND sender:
lncli sendpayment --keysend --dest <your-node> --data 34349334=<hex>,6789998212=<hex> --amt 5. From a CLN sender:lightning-cli keysend <your-node> 5000 <message> '[{"type":6789998212,"value":"<hex-of-csv-addresses>"}]'.
Batch keysend (only if opting in)
- Your node accepts keysend (LND
accept-keysend=true/ CLN keysend plugin loaded / LDK feature bit set / Eclairfeatures.keysend = optional). - You have a written fee policy (the percentage you keep) and your code reflects it.
- You have a written unknown-recipient policy (refund / drop / operator-bucket) and your code reflects it.
- You have sent the operator your domain, node pubkey, and confirmations from Batch keysend opt-in.
Reference examples
- Working reference subscriber (LND, Go):
examples/lnd/main.go - Example CLN plugin showing TLV parsing:
examples/cln-plugin/python-plugin/helloworld.py