feat(lnurl): LUD-21 verify on the LNURL-pay proxy callback#440
Merged
Conversation
The public GET /pay/lnurl/:username callback returned {pr} with no way
for the payer to confirm settlement — integrators (e.g. Hustle Link)
had to rely on the vendor noticing a push notification. LUD-21 closes
that loop:
- Callback response now includes verify:
<webhook-base>/pay/lnurl/verify/<paymentHash>
- New public GET /pay/lnurl/verify/:paymentHash returns
{status, settled, preimage, pr} per LUD-21, backed by IBEX
invoice-from-hash. settled = state SETTLED or settleDateUtc present;
preimage only revealed once settled.
- Unknown/malformed hashes (strict 64-hex validation, checked before
any IBEX call) and IBEX errors all map to LUD-21's
404 {status:"ERROR", reason:"Not found"}.
- Permissive CORS on verify only — the payment hash is an unguessable
capability and web wallets poll cross-origin. Same public rate limit
as the callback.
Benefits every app building on Flash lightning addresses.
5 tests; webhook-server suites green.
| router.get( | ||
| paths.verify, | ||
| publicLnurlRateLimit, | ||
| cors({ origin: true, methods: ["GET"] }), |
bobodread876
approved these changes
Jul 7, 2026
…dening Addresses the adversarial review: B2 (security): verify previously answered for ANY Flash/IBEX invoice via the org-scoped invoice-from-hash — disclosing bolt11 (amount, memo), settlement timing, and post-settlement preimages for invoices this proxy never issued, on the false premise that payment hashes are unguessable (routing nodes and anyone shown an invoice see them; zap receipts embed bolt11s publicly). Issued hashes are now recorded in LnurlInvoiceModel (unique index, 30d TTL) at callback time; verify 404s any hash not recorded, before any IBEX call — which also kills the garbage-hash IBEX amplification vector. B3: verify gets its own rate-limit bucket (60/min) — LUD-21 polling can no longer starve the payment callback's shared 120/min budget. N1: settled is now the single strict signal (state.id === 1, matching payment-status-checker); settleDateUtc no longer gates preimage release. N2: settled results (immutable) served from a bounded in-process cache. N3: LNURL convention — HTTP 200 with status:ERROR body, not 404 (some wallet libs treat non-2xx as transport failure). N4: outcome logging with hash prefixes on the verify route. Nits: hash lowercased before lookups; misleading "unguessable capability" comment corrected; spec relocated to routes/ with alias-based mocks. Tests 5 -> 12, including the previously-vacuous preimage-suppression case (now asserts a PRESENT preimage is withheld while OPEN), thrown-rejection path, scope-guard no-IBEX-call, ACCEPTED-state-with-settleDateUtc not settled, cache single-call, and case normalization. 102 webhook-server tests green.
Contributor
Author
|
Addressed the full adversarial review in the latest commit:
Not taken: supertest route-order pin (no supertest harness in repo — route order verified manually; 🤖 Generated with Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds LUD-21 payment verification to the public LNURL-pay proxy (
ibex.flashapp.me). The callback response now carries averifyURL, and the newGET /pay/lnurl/verify/:paymentHashreports settlement ({status, settled, preimage, pr}) backed by IBEXinvoice-from-hash(already wrapped inclient.ts).Payers can now confirm settlement programmatically — the immediate consumer is Hustle Link's WhatsApp agent (auto-announcing "⚡ Paid" instead of the vendor eyeballing a push notification), but it benefits every integrator on Flash lightning addresses.
Design notes:
settled= IBEX stateSETTLEDorsettleDateUtcpresent; preimage only revealed once settled404 {status:"ERROR", reason:"Not found"}Test plan
user@flashapp.meinvoice, poll the verify URL through settlement🤖 Generated with Claude Code