Reference implementation for NIP-88 Recurring Subscriptions with payment verification and subscription lifecycle management.
Built from and matches the production implementation running at nostreon.com. The event builders here are the exact same shape Nostreon publishes for real tiers and payment receipts.
Builds the four NIP-88 event kinds:
- kind 37001 — Subscription tiers with pricing, perks, and multiple cadence options
- kind 7001 — Subscribe events
- kind 7002 — Unsubscribe events
- kind 7003 — Payment receipts with
valid_untilfor deterministic subscription validity
- Create subscriptions with explicit
expires_atderived from cadence - Check validity against expiry (not zap scanning)
- Soft cancellation — access continues until expiry
- Expiry detection for cleanup cron jobs
Invoice-based payment flow as an alternative to zap-based verification:
Subscriber → pays Lightning invoice → BTCPay webhook → create subscription → publish kind:7003 receipt
The kind:7003 receipt includes a valid_until tag so any client can verify subscription validity without scanning zap history across relays.
DM-based reminders sent at 7 days and 1 day before expiry.
The NIP-88 draft relies on zap receipts for subscription verification. In production, this has issues:
- Zaps require the subscriber's client to be online with a wallet connected
- Failed zaps are silent — no one knows a payment didn't go through
- Scanning for zap receipts across relays is slow and unreliable
- No explicit expiry — clients must compute validity from zap timestamps and cadence
Invoice-based payments with a kind:7003 receipt solve these:
- Payment confirmation is server-side (webhook), not client-dependent
- Failures are detected immediately and can trigger retries
valid_untilmakes subscription validity a simple timestamp check
import {
buildTierEvent,
buildSubscribeEvent,
buildPaymentReceiptEvent,
createSubscription,
isSubscriptionValid,
} from "nip88-subscriptions";
// Create a tier with monthly and annual pricing
const tier = buildTierEvent({
name: "Supporter",
description: "Access to all premium content",
perks: ["Premium articles", "Subscriber chat", "Early access"],
amounts: [
{ amount: 500, currency: "usd", cadence: "monthly" }, // $5/month
{ amount: 5000, currency: "usd", cadence: "yearly" }, // $50/year (2 months free)
],
relayUrl: "wss://premium.example.com",
});
// After payment is confirmed, create subscription
const sub = createSubscription(
"subscriber-hex-pubkey",
"tier-uuid",
"monthly",
"btcpay-invoice-id"
);
// Publish a payment receipt so any client can verify
const receipt = buildPaymentReceiptEvent({
subscriberPubkey: "subscriber-hex-pubkey",
subscribeEventId: "kind-7001-event-id",
amountPaid: 500,
currency: "usd",
validUntil: Math.floor(new Date(sub.expires_at).getTime() / 1000),
invoiceId: "btcpay-invoice-id",
});
// Check if a subscription is valid
console.log(isSubscriptionValid(sub)); // true┌──────────┐ ┌─────────────┐ ┌──────────────┐ ┌───────────┐
│Subscriber│────▶│ Platform │────▶│ BTCPay │────▶│ Lightning │
│ Client │ │ (creates │ │ (invoice + │ │ Network │
│ │ │ invoice) │ │ webhook) │ │ │
└──────────┘ └──────┬──────┘ └──────┬───────┘ └───────────┘
│ │
│ webhook confirms │
│◀───────────────────┘
│
▼
┌─────────────────┐
│ 1. Create sub │
│ 2. Publish 7003 │
│ 3. Grant relay │
│ access │
└─────────────────┘
- NIP-88 Recurring Subscriptions PR
- Nostreon/relay-auth — NIP-42 relay access control for gating subscriber content
- NIP-XX: Relay Access Control — proposed NIP for standardizing relay access control
MIT