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:
- Create a Product named "Pro Toolkit".
- Add a Plan named "Lifetime Access".
- On the "Lifetime Access" Plan, attach an Entitlement with the value
pro_features. - 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.
- 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.
| Parameter | Required | Description |
|---|---|---|
owner | Yes | An 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. |
planId | Yes | The Lifetime Access Plan. |
interval | Yes | null for a one-off purchase. |
intervalCount | Yes | null for a one-off purchase. |
grantee | No | The individual who receives access, typically the buyer's user ID. Omit it and Salable creates a Group for the owner. |
currency | No | The purchase currency, for example USD. If omitted, it is resolved from the customer's geolocation at checkout. |
successUrl | No | Where Stripe returns the customer after payment. Optional — if omitted here and in Product Settings, Salable's hosted success page is used. |
cancelUrl | No | Where 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
granteeto the team's Salable Group ID (prefixed withgrp_); 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.urlRun the flow end to end with Stripe's test card 4242 4242 4242 4242 and any future expiry and CVC. After paying:
GET /api/subscriptionsshows the new Subscription withisPerpetual: trueand anullinterval.GET /api/receiptsreturns a Receipt for the $199 charge. Areceipt.createdwebhook 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.