Operational Checklist#

Run through this before flipping your integration on for production users.

LNURL-pay endpoint#

  • /.well-known/lnurlp/<user> returns commentAllowed >= 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 LND ValueMsat / CLN amount_msat / Eclair amountMsat, 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 a getreceivedinfo sweep of the recent window).
  • Your subscriber reads TLV 6789998212 and 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.com is 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 / Eclair features.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