Skip to content

Add NIP-63a: Minimal Payment Gateway Descriptor#2315

Open
mostafa-khaldi wants to merge 8 commits intonostr-protocol:masterfrom
mostafa-khaldi:drafts
Open

Add NIP-63a: Minimal Payment Gateway Descriptor#2315
mostafa-khaldi wants to merge 8 commits intonostr-protocol:masterfrom
mostafa-khaldi:drafts

Conversation

@mostafa-khaldi
Copy link
Copy Markdown

This NIP introduces a new event kind 10164 that allows content creators to declare payment gateways, pricing models, and subscription rules for accessing paid content.

@fiatjaf
Copy link
Copy Markdown
Member

fiatjaf commented Apr 14, 2026

This is kinda weird with the @pablof7z unrelated commits in there.

Anyway, I'm not sure it is worth standardizing methods for receiving payments on Nostr like that, but as far as a standard for payments goes I think this gets pretty close to optimal.

I'm not experienced in this stuff though, so feel free to ignore my opinion.

@mostafa-khaldi
Copy link
Copy Markdown
Author

I think the branch I created was somehow behind the mainstream repo, resolving the conflicts that have occurred caused the merging of old commits, weird tho.

@TheIcarusWings
Copy link
Copy Markdown

TheIcarusWings commented Apr 14, 2026

Thanks for pushing on this, the payment layer is exactly the gap NIP-63 leaves open and it's worth filling. A few pieces of feedback from building a paid-content platform on Nostr:

1. Link to the tier event. Right now 10164 floats free of NIP-63. A client reading a gated note has no way to know which 10164 unlocks it. I'd add a required a tag pointing at the NIP-63 tier (or equivalent addressable event) so the relationship is explicit:

["a", "30000:<creator_pubkey>:<tier_d_tag>", "<relay>"]

Without this, discovery only works if there's exactly one gateway per creator, which breaks as soon as someone offers different payment options per tier.

2. Native rails should be first-class, not behind u. The strength of doing this on Nostr is that a wallet-enabled client can pay a Lightning invoice, BOLT12 offer, or Cashu request inline. A single u URL pushes everyone back to a web checkout and re-centralizes the flow. I'd suggest:

  • lightning method carries a LNURL-pay or BOLT12 offer directly in the tag
  • cashu / nutzap method carries a mint list
  • u becomes an optional fallback for rails that genuinely need a hosted checkout (Stripe, PayPal)

3. Amounts need precision and asset discipline. Mixing "0.0002" BTC and "10" USD as opaque strings will cause rounding bugs in every client that implements this. Proposal:

["price", "<m>", "<p>", "<label>", "<amount>", "<asset>", "<interval>"]

Where amount is an integer in the asset's minor unit (sats for BTC/Lightning, cents for USD, etc.) and asset is explicit. Drop the separate currency tag, it becomes redundant.

4. The method type enum will ossify. Hard-coding ["bitcoin", "lightning", "ethereum", ...] in the spec means every new rail needs a NIP revision. Either reference an external registry or make type a free-form string with a recommended (not exhaustive) list.

5. Replaceable vs addressable is ambiguous. Kind 10164 is in the replaceable range, so a creator can only publish one. But the PR says "one event = one gateway" and shows creators with both BTCPay and Stripe, which implies multiple. I'd move this to the addressable range (30000-39999) with a d tag identifying the gateway, so a creator can publish one per provider.

6. Missing fields real subscription billing needs:

  • Trial period
  • Grace period after failed renewal
  • Plan expiry / availability window (for limited-time offers)
  • Whether a plan is active or deprecated (so old subscribers keep their grandfathered price)

7. Discounts feel under-specified. No coupon codes, no expiry, no per-subscriber targeting, no stacking rules. I'd either flesh this out or drop it from v1 and add it in a follow-up, otherwise every client will interpret it differently.

Happy to collaborate on a revised draft if useful. We're shipping a creator platform against NIP-63 right now and running into exactly these questions.

@mostafa-khaldi
Copy link
Copy Markdown
Author

hey @Joaofcm this is a great feedback, I agree with most of your mentioned points, however there are these two

  1. Native rails should be first-class, not behind u. The strength of doing this on Nostr is that a wallet-enabled client can pay a Lightning invoice, BOLT12 offer, or Cashu request inline. A single u URL pushes everyone back to a web checkout and re-centralizes the flow. I'd suggest:

lightning method carries a LNURL-pay or BOLT12 offer directly in the tag
cashu / nutzap method carries a mint list
u becomes an optional fallback for rails that genuinely need a hosted checkout (Stripe, PayPal)

The native payment in Nostr is not behind but rather within the system, if we prioritize lightning, cashu..etc, we might discourage other users to use the gateway, tho you have mentioned a fallback, but this makes the providers having a special treatment for them (or let's say making them a mandatory payment methods at first) instead of making them optional. In anyway, I don’t see separating native Nostr payments from the others have any real benefits where they can exist anyway.

For the second one

  1. The method type enum will ossify. Hard-coding ["bitcoin", "lightning", "ethereum", ...] in the spec means every new rail needs a NIP revision. Either reference an external registry or make type a free-form string with a recommended (not exhaustive) list.

Where this is partially true, I think having a set of types that we define would eliminate spelling redundancy and adding others as a type can allow to put any uncommon method falls to that category, nonetheless, this is just an informative data for displaying and the gateway can detect what it offers with what the creator chose, without adding any NIP specifications for each method, at least this is how I see it.

Feel free to add up or share your thoughts.

@TheIcarusWings
Copy link
Copy Markdown

Thanks Mostafa, happy to dig in on both.

On point 2: I don't think this framing is about priority or mandating anything, it's about what a client can actually execute. As written, u is "Clients redirect users here after selection", so every payment, even Lightning, ends in a browser handoff. A BOLT12 offer or LNURL-pay string in the method tag is different: a wallet-enabled client can pay it programmatically, the same way Nostr wallets already resolve zap callbacks and nutzap mint URLs.

A creator who only wants Stripe still publishes one method with u and nothing changes for them. A creator offering Lightning just also gets the inline-pay path on clients that support it. Gateway providers aren't being downgraded, they're handling the rails that genuinely need a hosted checkout, which is what they're good at.

If everything lives behind u, a wallet-enabled Nostr client can't do anything a browser couldn't do, and the spec collapses to "publish a checkout URL on Nostr." Still useful, but a narrower claim than what the protocol is capable of.

On point 4: Adding others actually concedes the point. Once others is valid, type is effectively free-form from a parsing perspective, just with worse ergonomics, because clients can't tell two different others entries apart.

A recommended (not exhaustive) list gets you the spelling discipline without the ossification cost: "clients SHOULD use these canonical strings: bitcoin, lightning, cashu, ethereum, ..." Same consistency for common rails, and when someone ships a new rail three years from now they pick a string and it works, no NIP revision needed. That's also the pattern most NIPs follow for extensible categories.

The informative-only framing is fair, but even a display-only closed enum has a cost: every new rail has to wait on spec updates to render correctly in clients that match against the enum.

@mostafa-khaldi
Copy link
Copy Markdown
Author

Hey @TheIcarusWings, thanks for the elaboration,

On point 2:
I’d like to clear up a few issues that we need to find a solution for if we follow your suggested approach. While I get your point here, the entire idea of this NIP is to centralize a bunch of payment methods on each gateway, and these gateways can also provide a lightning/cashu method.

If we remove these two from the list of methods for a specific gateway u, there is no way for it to provide lightning/cashu payments in this setup. Although all the data about it can exist in another event that expresses the intention of the user to accept payments via lightning/cashu using the lightning, cashu tags, the gateway would then need to query all events and search for this one, instead of just querying the one related to it using u and finding the relevant information there.

I’d like to suggest the following, we can either do one of the two:

  • Accept both specs, leaving both lightning, cashu, and u as gateway definitions, and also allow lightning, cashu as method types for a specific gateway u
  • Leave only u as a gateway definition, and clients should dig through the accepted methods. If lightning and cashu exist, they can use the user’s lud16/lud06 or fetch the mint URLs for the payment inline (though we can alter the spec to include those within the method array)

On point 4:
We can do that, define a list and still allow custom types as well.

@mostafa-khaldi
Copy link
Copy Markdown
Author

@TheIcarusWings Another issue on point 2, is how and which payment gateway verifies and updates the subscribers list if the payment is done in another app (in this case the nostr client)? Adding the lightning method for a specific gateway u is not just an indication of accepting payment via that method, but also is a custom way for that gateway to publish a custom event, or add extra properties coming for the checkout session to actually verify the related payment (if intended for subscription) or just a normal zap. This is the biggest issue here, unless the nostr client is also the gateway for this that auto-manages the subscribers list.

By the way, this NIP was mainly proposed for auto-management haha

@TheIcarusWings
Copy link
Copy Markdown

Thanks Mostafa, useful refinement.

On point 4: Agreed. Canonical list plus allow custom types is the right shape, solves ossification without losing spelling discipline.

On point 2: Option (a) is the cleaner path. Keep u for gateway-hosted flows, allow inline lightning / cashu as optional siblings. A creator who only wants Stripe still ships one u entry and nothing changes. A creator who wants programmatic Lightning gets both paths, and clients pick based on capability.

On the verification question (your follow-up): I don't think inline methods break auto-management, they just move the verification surface. The gateway doesn't need to observe a browser redirect to learn about a settled payment. If the gateway controls the endpoint (its own LN node, its own LNURL-pay, its own nutzap pubkey), it already sees settlement:

  • Lightning: the gateway's node fires an invoice-paid webhook on settlement. Same shape as a Stripe webhook, just from its own LND/CLN rather than Stripe's servers. Payment-hash-to-tier mapping lives in the gateway DB like any other rail.
  • Cashu / nutzap: the gateway subscribes to 9321 events tagged to its pubkey on the relays in the event. The receipt is the verification signal, publicly auditable on Nostr.
  • Zaps to a lud16 the gateway controls: same pattern, subscribe to 9735 receipts with the gateway's pubkey in the p tag.

Mental model: u means rails verified in the gateway's web stack (Stripe webhook, BTCPay API). Inline methods means rails verified via the gateway's own LN node or Nostr subscriptions. In both cases the gateway owns the endpoint and observes settlement. The only thing that changes is whether the user's client talks to the gateway's web UI or to its LN/mint rails directly.

The one flow this genuinely doesn't cover is a creator publishing their personal lud16 with no gateway in the loop. That's out of scope by design though, a creator doing that isn't using auto-management, they're just accepting zaps.

@mostafa-khaldi
Copy link
Copy Markdown
Author

Hey @TheIcarusWings

I get your point, but I think the scope is too tight here, see, the creators will be allowed to use any lightning address they wish, so it's not necessary for the gateway to control that part, but if we're gonna go with this logic, I think we need to assure these two:

  • All payments via lightning/cashu should always be published to Nostr, with the following included (p of the creator, P of the gateway)
  • The gateway must have a pubkey which should be included as a tag in the payment descriptor event so it can be used for this purpose and it could track these payments server side using the two conditions.

Also, we should keep in mind that this NIP introduces gateways that can auto-manage and communicate with Nostr, others for merely payment should use the announcement event kind:10163 which redirects for payment and the creators manage the list themselves.

Updated the event kind from `10164` to `30164` which makes it a replaceable event, and added support for custom payment method type if out of the proposed list.
kai-familiar

This comment was marked as spam.

@TheIcarusWings
Copy link
Copy Markdown

Good refinements, both land.

On the receipt invariant (P = gateway on 9735/9321): this is the right shape. It means the spec doesn't have to care whether the LN/Cashu endpoint is gateway-run, creator-run, or third-party. What matters is the receipt carrying the gateway's pubkey so the gateway can subscribe and attribute. Creators keep the freedom to use any lud16 or mint they want. Broader than my earlier "gateway owns the endpoint" framing, and cleaner for it.

On the gateway pubkey tag in 10164: agreed, make it required. Either a dedicated ["gateway", "<pubkey>"] or a reused ["p", "<pubkey>", "", "gateway"]. Gives clients the pubkey to include as P on the payment, and the gateway the filter for its subscriber-building query.

Worth noting this composes cleanly with my earlier point 5 (addressable kind): if the gateway pubkey is required anyway, it's the natural d tag, which is also how one creator declares multiple gateways without replaceable-event collisions.

On 10163 vs 10164: worth calling out in the NIP rationale. One-liner along the lines of: "10163 announces payment availability when the creator manages subscribers themselves. 10164 declares a gateway that auto-manages subscribers and requires receipts to carry its pubkey as P." Clarifies when to use which and keeps scope tight.

One small thing to nail down: whether the gateway-identifier on the receipt side is a plain P or a dedicated tag. P is used loosely in a few other NIPs, so a dedicated one may be easier for gateways to filter on without false positives.

@TheIcarusWings
Copy link
Copy Markdown

Catching up on commit b136525 now, didn't see it before my previous reply went up.

Moving to 30164 addresses point 5 cleanly, and custom method strings wrap up point 4. Nice progress.

One refinement on the d tag: <payment_url | unique_identifier> is quite loose. If we settle on the gateway-pubkey-as-required-tag invariant from my comment just above, the gateway pubkey is the natural d-tag value:

  • stable across URL rotations
  • makes (kind, creator_pubkey, gateway_pubkey) a meaningful tuple ("creator X's descriptor for gateway Y")
  • gives gateways a trivial filter: kind:30164, #d:<gateway_pubkey>
  • a "random unique string" carries no semantic value, and a URL can change

Happy to see this land without that change too, but using the pubkey ties the addressable kind and the receipt-attribution flow into one coherent piece.

The other open items from my previous comment (receipt invariant with P=gateway on 9735/9321, the 10163 vs 30164 scope line) still stand and should slot into the next revision.

@mostafa-khaldi
Copy link
Copy Markdown
Author

Hey @TheIcarusWings I think having the gateway pubkey now solves many issues, and I agree also to use it as a d tag which makes it a unique unchangeable identifier, will try to apply few changes to the proposal, again, thanks for the feedback!

Updated the identifier section to replace the payment URL with the gateway public key. Added details about the gateway's Nostr pubkey and its usage in payment-related events.
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.

5 participants