Perpetual Subscriptions Quick Start

Depending on your application, a recurring charge may not always be the best payment model; for some you'd rather take a single payment upfront and give them permanent access to your app. Building that yourself means provisioning access with no renewal date to track, then recording the payment as proof of purchase.

Introduction

A one-off Line Item handles the charge. Once the payment completes, the customer gets a Perpetual Subscription that never renews or expires, unlocking the Plan's Entitlements, along with a Receipt recording the payment. Entitlement checks then return the unlocked features with no expiry for as long as the Subscription stays active, which is until you cancel it.

The examples in this guide use a Pro Toolkit Product with a Lifetime Access Plan, a $199 one-off Line Item, and a pro_features Entitlement, but the same structure applies to any one-off product.

Pro Toolkit (Product)
└─ Lifetime Access (Plan)
   └─ Pro Toolkit License — $199 one-off (Line Item)
      └─ unlocks: pro_features (Entitlement)

1: Create the Product, Plan, and Entitlement

Define what you're selling, set its one-time price, and choose what it unlocks.

In the dashboard:

  1. Create a Product named "Pro Toolkit".
  2. Add a Plan named "Lifetime Access".
  3. On the "Lifetime Access" Plan, attach an Entitlement with the value pro_features.
  4. Add a Line Item called "Pro Toolkit License", set its Interval Type to "One‑off" (a single charge with no billing interval), and price it at $199.
  5. Check the Create a perpetual subscription checkbox.

The pro_features Entitlement is what your application checks at runtime — attach one Entitlement per feature on the Plan so you can move features between Plans later without touching your application code.

2: Send the customer to checkout

Generate a Stripe Checkout link for the lifetime Plan. Setting interval and intervalCount to null is what tells Salable this is a one-off purchase rather than a recurring one. This will generate a link at data.url to redirect the customer to. When they pay, a Perpetual Subscription is created (isPerpetual: true, no interval, no expiry) along with a Receipt for the $199 charge.

ParameterRequiredDescription
ownerYesAn owner is the tenant the subscription and receipt will belong to. Usually a team, organization, or workspace ID, or the user ID for single-user products.
planIdYesThe Lifetime Access Plan.
intervalYesnull for a one-off purchase.
intervalCountYesnull for a one-off purchase.
granteeNoThe individual who receives access, typically the buyer's user ID. Omit it and Salable creates a Group for the owner.
currencyNoThe purchase currency, for example USD. If omitted, it is resolved from the customer's geolocation at checkout.
successUrlNoWhere Stripe returns the customer after payment. Optional — if omitted here and in Product Settings, Salable's hosted success page is used.
cancelUrlNoWhere the customer returns if they abandon checkout. Optional — if omitted here and in Product Settings, Salable's hosted page is used.

Note A lifetime Plan without a Per-seat Line Item will only grant access to a single Grantee. To sell a shared lifetime deal to a team, add a per-seat Line Item to the Plan and set grantee to the team's Salable Group ID (prefixed with grp_); see Per-Seat Billing.

import { Salable } from '@salable/sdk';
const salable = new Salable(process.env.SALABLE_SECRET_KEY);
 
const { data } = await salable.api.checkout.post({
    owner: 'user_ada',
    planId: 'plan_01HXLIFETIME',
    grantee: 'user_ada',
    interval: null,
    intervalCount: null,
    currency: 'USD',
    successUrl: 'https://yourapp.com/welcome',
    cancelUrl: 'https://yourapp.com/pricing'
});
 
// Redirect user to data.url

Run the flow end to end with Stripe's test card 4242 4242 4242 4242 and any future expiry and CVC. After paying:

  • GET /api/subscriptions shows the new Subscription with isPerpetual: true and a null interval.
  • GET /api/receipts returns a Receipt for the $199 charge. A receipt.created webhook also fires when the Receipt is generated, which is the reliable signal that the payment went through.
  • No renewal is ever scheduled, so the access persists until you explicitly cancel the Subscription.

3: Add entitlement checks to your application

In your application, check whether the grantee has access before unlocking the paid features. Because a Perpetual Subscription never ends, the check returns pro_features with a null expiry for as long as the Subscription stays active.

import { Salable } from '@salable/sdk';
const salable = new Salable(process.env.SALABLE_SECRET_KEY);
 
const { data } = await salable.api.entitlements.check.get({
    queryParameters: { granteeId: 'user_ada' }
});
 
const hasProFeatures = data.entitlements.some(entitlement => entitlement.value === 'pro_features');

Next steps

  • Subscriptions & Billing How Perpetual Subscriptions behave, and the Salable Only variant for granting lifetime access without a payment.
  • Cart and Checkout Bundling one-off Line Items with recurring Plans, and one-off-only Carts.
  • Understanding Entitlements How access is resolved at check time and how to gate features on the result.