Skip to main content
Subscriptions let you charge customers on a recurring basis using Lightning payments. Customers pay once to start their subscription, then receive renewal reminders before each billing period.

What are Subscriptions?

Subscriptions in moneydevkit work differently from traditional card-based subscriptions:
  • No stored payment methods - Lightning payments are push-based, so customers pay proactively
  • Renewal reminders - Customers receive emails 7, 4, and 1 days before their subscription renews
  • Grace period - If a customer misses a payment, they have 3 days to renew before cancellation
  • Instant settlement - Payments settle immediately in Bitcoin

Setup

1

Create a recurring product

In your moneydevkit dashboard, create a product with a recurring interval:
  1. Navigate to Products
  2. Click the + button
  3. Fill in product details
  4. Select a Recurring interval: Monthly, Quarterly, or Yearly
  5. Click Create Product
2

Create a subscription checkout

Use the recurring product ID when creating checkouts:
import { useCheckout } from '@moneydevkit/nextjs'

function SubscribeButton() {
  const { createCheckout, isLoading } = useCheckout()

  const handleSubscribe = async () => {
    const result = await createCheckout({
      type: 'PRODUCTS',
      product: 'your-recurring-product-id',
      successUrl: '/checkout/success',
      customer: {
        externalId: user.id,  // Link to your user
        email: user.email,
        name: user.name,
      },
    })

    if (result.error) {
      console.error(result.error.message)
      return
    }

    window.location.href = result.data.checkoutUrl
  }

  return (
    <button onClick={handleSubscribe} disabled={isLoading}>
      Subscribe
    </button>
  )
}
Subscriptions require customer data. Always pass customer.email so renewal reminders can be sent.
3

Check subscription status

Use the useCustomer hook to check if a user has an active subscription:
import { useCustomer } from '@moneydevkit/nextjs'

function PremiumContent() {
  const { customer, isLoading } = useCustomer({ externalId: user.id })

  if (isLoading) return <p>Loading...</p>

  if (!customer?.hasActiveSubscription) {
    return <SubscribeButton />
  }

  return <div>Premium content here</div>
}

Subscription Lifecycle

┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│  ┌──────────┐    payment    ┌────────┐    period ends   ┌────────┐ │
│  │ Checkout │ ───────────▶  │ Active │ ───────────────▶ │past_due│ │
│  └──────────┘               └────────┘                  └────────┘ │
│                                  │                           │     │
│                                  │ cancel                    │     │
│                                  ▼                           │     │
│                             ┌────────┐    3 day grace        │     │
│                             │canceled│ ◀─────────────────────┘     │
│                             └────────┘                             │
│                                  ▲                                 │
│                                  │ renewal payment                 │
│                                  │                                 │
│                             ┌────────┐                             │
│                             │ Active │ (extended)                  │
│                             └────────┘                             │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
StatusDescription
activeSubscription is active and within the current billing period
past_dueBilling period ended, customer is in the 3-day grace period
canceledSubscription has been canceled (either by customer or after grace period)

Checking Subscription Status

The useCustomer hook fetches customer data including their subscriptions:
import { useCustomer } from '@moneydevkit/nextjs'

function AccountPage() {
  const { customer, isLoading, error, refetch } = useCustomer({ externalId: user.id })

  if (isLoading) return <p>Loading...</p>
  if (error) return <p>Error: {error.message}</p>
  if (!customer) return <p>Customer not found</p>

  return (
    <div>
      <h2>Subscription Status</h2>
      {customer.hasActiveSubscription ? (
        <p>You have an active subscription</p>
      ) : (
        <p>No active subscription</p>
      )}

      <h3>All Subscriptions</h3>
      <ul>
        {customer.subscriptions.map(sub => (
          <li key={sub.id}>
            {sub.status} - {sub.recurringInterval}
          </li>
        ))}
      </ul>
    </div>
  )
}

CustomerIdentifier Types

You can identify customers in three ways:
IdentifierDescriptionExample
externalIdYour app’s user ID{ externalId: user.id }
emailCustomer’s email address{ email: '[email protected]' }
customerIdmoneydevkit customer ID{ customerId: 'cus_abc123' }

Grace Period

When a subscription’s billing period ends without payment:
  1. Day 0 - Status changes to past_due, first grace period email sent
  2. Day 1 - Second grace period reminder
  3. Day 2 - Third grace period reminder
  4. Day 3 - Subscription auto-cancels if no payment received
During the grace period, customers can still renew their subscription by clicking the renewal link in their email.

Renewal Reminders

Customers receive renewal reminder emails at:
  • 7 days before the billing period ends
  • 4 days before the billing period ends
  • 1 day before the billing period ends
Each email includes a link for the customer to renew their subscription.

Recurring Intervals

IntervalValueBilling Cycle
MonthlyMONTHEvery 1 month
QuarterlyQUARTEREvery 3 months
YearlyYEAREvery 12 months
Billing dates use calendar math. A subscription started on January 31st will renew on February 28th (or 29th in leap years), then March 31st, and so on.

Price Units

Product prices are returned in base currency units:
  • USD: cents (divide by 100 for dollars)
  • SAT: satoshis (no conversion needed)
const { products } = useProducts()
// USD prices are in cents - divide by 100 for display
const displayPrice = (product.prices[0]?.priceAmount ?? 0) / 100