Skip to content

Offers: add pull-based Payment Status Request (LUD-21-equivalent verify) - #17

Open
nothing-stops-this-train wants to merge 1 commit into
shocknet:mainfrom
nothing-stops-this-train:payment-status-request
Open

Offers: add pull-based Payment Status Request (LUD-21-equivalent verify)#17
nothing-stops-this-train wants to merge 1 commit into
shocknet:mainfrom
nothing-stops-this-train:payment-status-request

Conversation

@nothing-stops-this-train

Copy link
Copy Markdown

Motivation

The Payment Receipt added to the Offers spec is exactly the right primitive — {"res":"ok","preimage":...} is everything a verifier needs — but its delivery model is push-only: it is published once, at settlement time, as an ephemeral kind 21001 event. Any client not subscribed at that precise moment can never recover it.

That excludes an important class of integrations: merchant and point-of-sale software, LNURL-pay bridges (e.g. bridgelet-style services wanting to offer LUD-21 verify), and other request-scoped or stateless clients — anything running on ordinary web hosting where no persistent Nostr subscription can be held open across the payment's lifetime. Today those integrations have no reliable way to confirm that an invoice they relayed was actually paid.

We specifically encountered this when trying to add noffer support to our merchant software (https://github.com/BareBits/cashupayserver). On shared web hosting, we can't have long-running threads waiting for a kind 21001 event. Flakes due to restarted threads, brief network downtime, etc can prevent an invoice from showing as paid.

With this proposal, we can enable the following scenario:

  • Merchant uses lightning wallet like Electrum with clink plugin to receive payments. This wallet can be online, behind a NAT. https://github.com/BareBits/electrum_clink
  • Merchant uses web-based e-commerce software to make sales
  • E-commerce software uses CLINK to direct lightning payments to the merchant's electrum wallet.

Proposal

A Payment Status Request: a pull-based query answered with the existing receipt payload on demand.

  • Request: kind 21001 to the receiving service, encrypted payload {"status_of": "<original_invoice_request_event_id>"}. Distinguished from an invoice request by the presence of status_of / absence of offer.
  • Response, one of:
    • settled (Lightning): the unchanged receipt payload {"res":"ok","preimage":...}, with a RECOMMENDED bolt11 echo so verifiers can bind the preimage to the payment hash statelessly;
    • settled (internal): {"res":"ok"}, unchanged;
    • not settled: {"res":"pending"};
    • unknown / state expired: new error code: 6 (Not Found).
  • Services that don't implement it naturally degrade via the existing code: 4 (Unsupported Feature).
  • Verifiers SHOULD check sha256(preimage) against the invoice payment hash.
  • Retention guidance: keep state through invoice expiry and ≥24h past settlement.
  • Authorization: knowledge of the original request event id acts as a bearer capability, analogous to possession of a LUD-21 verify URL; services MAY restrict responses to the original requesting pubkey.

No new payload semantics are introduced — the only new capability is being able to ask for the status/receipt instead of having to catch it mid-air.

Open questions for maintainers

  • Should the reference be the request event id, or (also) the invoice payment_hash? The event id is private-by-default; the payment hash would let anyone holding the invoice query status, which more closely matches LUD-21's semantics but widens who can obtain the preimage.
  • Is code: 6 the right number, or should Not Found reuse an existing code?
  • Whether the bolt11 echo should also be added to the push receipt for symmetry.

The Payment Receipt is push-based and ephemeral: published once at
settlement time, it is unrecoverable by any client not subscribed at
that exact moment. Merchant/point-of-sale software, LNURL-pay bridges,
and other request-scoped or stateless integrations therefore have no
reliable way to verify settlement, unlike LNURL's LUD-21 verify.

This adds a Payment Status Request: a kind 21001 query referencing the
original invoice request event id, answered with the existing receipt
payload on demand ("res":"ok" + preimage), "res":"pending" when unpaid,
or a new code 6 (Not Found) error. Services that do not support it
answer with the existing code 4 (Unsupported Feature).

No new payload semantics are introduced; only the ability to request
the status/receipt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5zaCAS4EwuvG4cVitT3T5
@shocknet-justin

Copy link
Copy Markdown
Member

Thanks for the write-up, interesting idea and I will ponder this a bit.

First instinct is nmanage may be the better option for your shop use-case (it's intended for shops to manage offers directly so users can create products from your UI). Even just callback_url on a manually created offer might be enough, that can deliver the receipt to the web host without a live sub.

LNURL-Verify mainly exists because LNURL isn't bidirectional, it can only respond, not push. A nostr service handing out the invoices can push, so it's more of a workaround for the backend than the client.

Is the idea that there's no service running at all and just static files, so callback is off the table? I'd think in that case you'd have to get the invoices client-side anyway, so I'm still a little confused how the follow-up is handled but not the sub.

Couple things that come to mind from when we pushed the receipt: invoice GC (LND in our case) and extra weight on the server for tracking event ids. GC could make a paid invoice look like a miss if the follow-up lands after expiry / the receipt window.

Will chew on it, mainly need to sketch how much has to go into Pub to make it reliable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants