# Salable
> Salable handles the entire billing and subscription lifecycle so you can focus on building your product.
>
> You get the flexibility to experiment with pricing — flat-rate subscriptions, per-seat licensing, usage-based metering, or hybrid models — without rewriting your billing logic each time. Powered by Stripe, Salable manages products, plans, line items, checkout, subscriptions, invoicing, and entitlements (feature access control) through a single integration.
>
> Salable takes care of the Stripe webhook complexity you'd otherwise build yourself — subscription lifecycle events, payment failures, invoice updates, plan changes — so your engineering time goes toward features, not billing plumbing.
>
> Everything runs through infrastructure designed for real SaaS businesses: a dashboard for managing your billing configuration, a REST API and webhooks for deep integration, a shopping cart for multi-plan checkout flows, and an MCP server that lets AI agents and tools like Claude Code create and manage your billing setup directly.
## Docs
### Salable Quick Start Guide
Source: https://beta.salable.app/docs/quick-start
# Salable Quick Start Guide
## Step 1: Enable Test Mode
Toggle **[Test Mode](/docs/core-concepts#test-mode-vs-live-mode)** on using the switch at the bottom left. While we're in Test Mode, anything we create throughout this guide will be strictly test data.
## Step 2: Create and Set Up a Payment Integration
In Salable, **Payment Integrations** allow us to accept payments for our Products and are powered by Stripe.
Click the Payment Integrations tab in your sidebar and click the Create Payment Integration button. You will be taken to the Stripe Connect onboarding flow. After completing the onboarding process, you will be redirected back to Salable.
> **Note** For this guide, you will only have to complete the Business Type form and the Personal Details section. To take live payments, you would also need to enter your banking details and verify your identity as instructed throughout the onboarding process.
## Step 3: Create a Product
A **[Product](/docs/core-concepts#product)** on Salable contains your pricing model, Plans, and features. Click on the Products tab on the sidebar to navigate to the Products page. Provide a name for your Product in the Product Name field (_eg_ "My SaaS Product") and click the Create Product button.
## Step 4: Create a Plan
**[Plans](/docs/core-concepts#plan)** allow you to define a payment model and the Entitlements you want to make available for it.
Click the Manage Product button (pencil icon) on your Product.
Provide a name for your Plan (_eg_ "Basic Plan") on the Plan Name field and click the Create Plan button.
## Step 5: Create an Entitlement
**[Entitlements](/docs/core-concepts#entitlement)** grant access to features (_eg_ `export_pdf`, `generate_images`) in your Product. When a user subscribes to a Plan, they receive these Entitlements.
To create an Entitlement, locate the Select Entitlements form field. Enter `entitlement_one` for your Entitlement name and click the (+) button to create and add the Entitlement to your Plan.
## Step 6: Create a Line Item
**[Line Items](/docs/core-concepts#line-item)** are the individual pricing components that constitute the overall payment model of your Plan. There are several types of Line Items, each with its own pricing structure. For this guide, we will create a **Flat Rate** Line Item that charges a fixed fee each billing cycle.
Click the Add Line Item button to pull up the Line Item form.
Provide a name for your Line Item in the Line Item Name field (_eg_ Monthly Subscription Fee). This is the name that will appear on Stripe invoices, so be sure to name your Line Items accordingly.
We will leave the Interval Type set to Recurring and the Price Type set to Flat Rate.
Next, we will set up a **[Price](/docs/core-concepts#price)**, set the Currency to USD, and set the Unit Amount to \$4.99. We will leave Interval set to Month and Interval Count set to one.
Click the Save Plan button to provide your Plan with your new Line Item.
## Step 7: Generate a Checkout Link
So far, we have set up a Payment Integration, created a Product, created a Plan, and assigned an Entitlement and a Line Item to the Plan. Now, let's purchase a **[Subscription](/docs/core-concepts#subscription)**.
Scroll below the Plan form to find another form that lets you add your Plan to your **[Cart](/docs/core-concepts#cart)**.
Select USD for the currency, month for the interval, and set the interval count to 1.
You will see two fields: Owner and Grantee. The **[Owner](/docs/core-concepts#owner)** should be an ID in your system that can be used to look up and manage the Subscription. The **[Grantee](/docs/core-concepts#grantee)** represents the entity that will be granted access to features in your application. Typically, the Grantee is a user ID. However, it could also be an organisation, team or any other entity ID ([Read more about Owners and Grantees here](/docs/core-concepts#access-control)).
Enter `owner_one_id` for the Owner field and click the (+) button, then enter `grantee_one_id` for the Grantee field.
Click the Add to Cart button and click Go to Cart to navigate to the Manage Cart page.
From here, you can review the contents of your Cart. When you're ready to proceed with the purchase, click the Checkout Cart button to begin **[Checkout](/docs/core-concepts#checkout)**.
## Step 8: Complete Test Checkout
On Stripe’s checkout page, enter the following test payment details:
- Email Address `someone@example.com`
- Card number: `4242 4242 4242 4242`
- Expiry: Any future date
- CVC: Any 3 digits
- Postal code/Zip code: Any code
> **Note** The actual fields may vary depending on your region
Click Subscribe to complete the checkout.
## Step 9: Verify Your Subscription
You'll be redirected to Salable and taken to the Subscriptions page. You should see your newly created Subscription with the Active status.
If you click on the Subscription to view details, you'll see:
- The Owner
- The associated Plan
- A list of **[Invoices](/docs/core-concepts#invoice)**, including a draft of the next Invoice
## Step 10: Check Entitlement Access
Let's perform an Entitlement Check with your newly created Subscription and Grantee to confirm they have access to the Entitlement.
- Navigate to the Entitlement Check tab
- In the Grantee ID field, enter `grantee_one_id`
- Click Check Grantee
You should see the following response:
```json
{
"type": "object",
"data": {
"entitlements": [
{
"value": "entitlement_one",
"type": "entitlement",
"expiryDate": "2026-01-02T17:28:41.000Z"
}
],
"signature": "3045022054188fb22b12a9e8565beda67a9859a7e3eb23e31f806a1dccf7b551267e46b9022100b29021c7b579e36a63d6b1b6c1e2be55c64a285a15223119ab1b17f88410047b"
}
}
```
This confirms that your Grantee has access to the Entitlement we created earlier.
## Conclusion
You have successfully:
- Created and onboarded a Payment Integration
- Created a Product
- Created a Plan
- Created an Entitlement
- Created a Line Item with a price, currency, and billing interval
- Successfully purchased a Subscription
- Performed an Entitlement Check on a Grantee
You are now familiar with the core concepts of Salable!
Still have questions? [Click here to review core concepts](/docs/core-concepts).
---
### Getting Started with Salable
Source: https://beta.salable.app/docs/getting-started-guide
# Getting Started with Salable
## What You'll Accomplish
By the end of this guide, you'll have a complete [Subscription](/docs/core-concepts#subscription) and [Entitlement](/docs/core-concepts#entitlement) management system ready and setup within Salable. You'll have a fully configured Stripe Connect account for payment processing, a [Product](/docs/core-concepts#product) with multiple [Plans](/docs/core-concepts#plan), and [Line items](/docs/core-concepts#line-item) with recurring or one-time charges. You'll add items to your [Cart](/docs/core-concepts#cart), create your first test Subscription, configure Entitlements that control feature access, and verify that your subscribed customer has the correct permissions. You'll also have multi-currency support configured, working checkout links, and API keys for integrating with your application.
---
## Step 1: Sign Up and Create Your Organisation
### 1.1 Create Your Account
Navigate to your Salable dashboard and click Sign Up to create an account and complete the authentication process. You'll be prompted to create or join an organisation.
### 1.2 Create Your Organisation
Organisations are how Salable manages multi-tenancy. All your Products, Plans, and Subscriptions are scoped to your organisation.
Enter a name for your organisation in the Name field and complete the organisation setup. You'll be redirected to your dashboard once the setup is complete.
> **Pro Tip** You can manage multiple organisations and switch between them using the organisation switcher in the sidebar.
### 1.3 Understanding Test Mode
You will notice the **[Test Mode](/docs/core-concepts#test-mode-vs-live-mode)** toggle in your sidebar. This allows you to experiment with pricing models safely, create test Subscriptions without real charges, and validate your integration in a test environment before going live. Keep Test Mode **ON** while following this guide.
---
## Step 2: Set Up Your Payment Integration
Before creating a Product, you need to set up a **[Payment Integration](/docs/core-concepts#payment-integration)**—the connection between Salable and Stripe that handles payment processing. This involves creating a [Stripe Connect](https://docs.stripe.com/connect/how-connect-works) account. If you have an existing Stripe account, you can connect it during onboarding.
> **Note** In Test Mode, you won't need to provide all onboarding details (banking, identity verification). However, **business type** and **personal details** are required even for test checkouts.
### 2.1 Create Your Payment Integration
In your dashboard sidebar, click Payment Integrations.
Click the Create Payment Integration button to begin the Stripe Connect onboarding process.
### 2.2 Complete Stripe Connect Onboarding
You will be guided through the Stripe Connect onboarding process step by step. Here you will be asked to enter various business and personal information, including:
- **Business Information**
Company name, business type, industry, and contact details.
- **Banking Information**
Bank account details for payouts. Not required in Test Mode.
- **Identity Verification**
Government-issued ID and business documents. Not required in Test Mode.
> **Important** The **business type** and **personal details** are required for checking out items even while in Test Mode. Without completing these forms, you will not be able to checkout the items we will create later in this guide.
**Review and Submit**
Submit your application. Stripe review typically completes within a few hours but can take up to a few business days.
### 2.3 Verify Integration Status
After onboarding, you'll return to the Payment Integrations page. Your integration status will show as **Active** (complete), **Incomplete**, or **Pending**. You can proceed with creating Products and Plans even if incomplete—full onboarding is only required for Live Mode payments.
> **Note** Account overview components are only available in **Live Mode**. If you're in Test Mode, you'll see a banner prompting you to switch to Live Mode for this step.
---
## Step 3: Create Your First Product
**[Products](/docs/core-concepts#product)** represent what you're selling and contain your Plans and pricing configuration.
### 3.1 Navigate to Products
In your sidebar, click Products to view your Products list.
### 3.2 Create a New Product
There are two ways to create a Product.
**Option A: Simple Form (Recommended)**
Enter the name for your Product in the Product Name field (_eg_ "My SaaS Platform") and click Create Product. Your new Product should appear in the table below.
**Option B: YAML Import (Advanced)**
Download the Product template by clicking Download Template, edit the YAML file with your Product configuration, and upload it using the Import button.
### 3.3 Configure Product Settings
Find your Product in the table and click the Edit button (pencil icon) to open configuration.
### 3.4 Configure Checkout Settings
Click the Settings accordion to expand configuration options.
Set up the checkout experience by configuring the Success URL (where customers go after a successful purchase, like `https://yourapp.com/welcome`) and the Cancel URL (where customers go if they abandon checkout, such as `https://yourapp.com/pricing`).
### 3.5 Additional Options
Configure optional settings based on your needs:
- **Allow Promo Codes at Checkout**:
Enable this to let customers apply discount codes during checkout. When enabled, a promo code field appears on the Stripe checkout page, allowing customers to enter promotional codes you've configured in your Stripe dashboard.
- **Collect Tax Automatically**:
Enable this to let Stripe calculate and collect taxes based on customer location. Stripe Tax automatically determines the correct tax rate and applies it to the invoice. This requires collecting the customer's billing address to determine their tax jurisdiction.
> **Important** This feature requires you to have opted in to Stripe Tax in your Stripe Dashboard. Enable and configure Stripe Tax in your Stripe account settings before using this option.
- **Collect Billing Address**:
Enable this to display billing address fields during checkout. This is required if you're using automatic tax calculation, as Stripe needs the customer's location to determine applicable tax rates. The billing address also appears on invoices and receipts.
- **Collect Shipping Address**:
If you're selling physical Products that require shipping, enable this option. It adds shipping address fields to the checkout flow, allowing customers to specify where Products should be delivered. This address is separate from the billing address.
- **Return Entitlements While Past Due**:
This controls feature access when a customer's payment fails, but their Subscription hasn't been cancelled yet. When enabled, customers maintain access to their Entitlements during the grace period while payment issues are being resolved. When disabled, access is immediately revoked when payments fail.
- **Card Pre-fill Preference**:
This setting controls how saved payment methods are handled at checkout. Choose None to always show an empty payment form, Choice to let customers select from saved cards or add a new one, or Always to use the customer's default payment method automatically.
### 3.6 Save Product Settings
Click Save to persist your changes.
---
## Step 4: Create Your First Plan
**[Plans](/docs/core-concepts#plan)** define a payment model and the [Entitlements](/docs/core-concepts#entitlement) available to subscribers. You might create Plans for different tiers (_eg_ Basic, Standard, Pro).
### 4.1 Create a Plan
Scroll to the Plans section and enter a name for your Plan in the Plan Name field (_eg_ "Starter Plan"). Click Create Plan and a Plan configuration form will appear.
### 4.2 Configure Plan Settings
Verify or update your Plan name and optionally enter a number of days for the Trial Period to offer a free trial.
### 4.3 Add a Tier Tag
Tier Tags prevent [Owners](/docs/core-concepts#owner) from purchasing multiple Plans that share the same tag—useful for making Plans mutually exclusive. Create a Tier Tag by entering a name in the Tier Tag field.
### 4.4 Add Entitlements
**[Entitlements](/docs/core-concepts#entitlement)** determine which features customers can access based on their Plan. Instead of manually managing feature flags, attach Entitlements to Plans and check them in your application.
In the Entitlements field, use the typeahead to search for existing Entitlements or create new ones. Enter a name (_eg_ `premium_features` or `api_access`) and click Create to add it to your Plan.
---
## Step 5: Add Line Items and Pricing
**[Line Items](/docs/core-concepts#line-item)** are the individual pricing components that make up your Plan's pricing model. A Plan can combine multiple Line Items—for example, a recurring monthly fee plus a one-time setup charge.
### 5.1 Understanding Line Item Types
Salable supports multiple pricing types for Line Items that you can mix and match within a single Plan:
#### Flat Rate
A fixed price charged per billing cycle, regardless of usage or quantity. Perfect for standard Subscription tiers like a \$29/month base Plan. This is the most common pricing model for SaaS applications.
#### Per-Seat
The price is multiplied by the number of seats or users in the Subscription. For example, \$10 per user per month means a team of 5 users pays \$50/month. This model scales naturally with team growth.
#### Metered
Usage-based billing charges are based on actual consumption during the billing period. Examples include \$0.01 per API call, \$0.50 per GB of storage, or \$5 per 1,000 emails sent. Customers only pay for what they use.
#### One-Off
A single charge that doesn't recur, commonly used for setup fees, onboarding charges, or one-off purchases. For example, a \$99 implementation fee is charged once when a customer first subscribes.
### 5.2 Add Your First Line Item
Click Add Line Item to begin. Under Basic Information, enter a descriptive name for the Line Item in the Line Item Name field (_eg_ "Monthly Subscription").
> **Important** Line item names appear on Stripe invoices, so use clear, customer-facing language. Avoid internal codes or technical jargon.
The **Slug** is auto-generated from the Line Item's name and must be unique in your organisation, but you can update it to another value if you prefer. The Slug is used as a pretty identifier for managing quantities in the cart and checkout.
You can optionally add a **Nickname** for internal reference, which won't be shown to customers.
You can optionally enable Allow Changing Quantities to let customers adjust quantities in the checkout.
Select Recurring for the Interval Type if charges should repeat each billing cycle (most common), or One-off for single charges, such as setup fees.
### 5.3 Choose Your Pricing Type
Select the pricing type that matches your billing model:
#### Flat Rate
Select Flat Rate as your pricing type. Optionally configure **Min Quantity** and **Max Quantity** to allow customers to purchase multiple units.
#### Per-Seat
Select Per-Seat as your pricing type, then choose a Billing Scheme:
- **Per Unit**: Multiplies your price by the seat count (_eg_ \$10 × 5 users = \$50/month)
- **Flat Rate**: Charges a fixed total regardless of seat count
- **Tiered**: Applies volume discounts or graduated pricing based on seat count
Configure **Min Quantity** and **Max Quantity** to set the allowed seat range. This helps differentiate pricing tiers—your Basic Plan might cap at 10 seats, while Pro requires a minimum of 11.
> **Important** Each Plan can only have one Per-Seat Line Item.
#### Metered
Select Metered as your pricing type, then choose a Billing Scheme:
- **Per Unit**: Multiplies your rate by actual usage (_eg_ \$0.01 × 1,000 API calls = \$10)
- **Tiered**: Applies volume or graduated pricing based on usage totals
Use the Select Meter typeahead to choose an existing meter or create a new one. Meters track usage and prevent double-billing.
#### Tiered Pricing (Per-Seat and Metered)
If you selected Tiered billing, choose a Tier Mode:
- **Volume**: All units charged at the rate of whichever tier the total falls into. Example: 0–10 units cost \$10 each, 11+ cost \$8 each. At 15 units, all 15 are charged at \$8 = \$120.
- **Graduated**: Different rates apply to each tier separately. Example: first 10 units at \$10 each, next 10 at \$8 each. At 15 units: (10 × \$10) + (5 × \$8) = \$140.
### 5.4 Configure Prices and Currencies
Configure prices for each billing interval (monthly, yearly) and currency (USD, GBP, EUR) you want to support.
#### Add a Price Interval
In your Line Item, click Add Price and select a Billing Interval from Day, Week, Month (most common), or Year.
> **Pro Tip** You can add multiple intervals. For example, offer both monthly (\$29) and yearly (\$290 – 17% discount) options.
#### Add Currency Options
For each interval, you can support multiple currencies. Click Add Currency and select a Currency from the dropdown (_eg_ USD, GBP, EUR etc). Enter the pricing based on your billing scheme.
#### For Per-Unit or Flat-Rate:
Enter the Unit Amount as the price (for example, 29 or 29.00 for \$29.00).
> **Note** Some currencies do not support decimal values and entering a decimal value may cause an error. For zero-decimal currencies such as JPY or KRW, only whole numbers are allowed (_eg_ 1000 for 1000 JPY).
#### For Tiered Pricing:
Configure each tier by setting the First Unit (auto-calculated from the previous Tier), entering the Last Unit as the upper limit (or "inf" for the final tier), and specifying the Unit Amount as the price per unit in this Tier. You can optionally add a Flat Amount as a base fee for reaching this Tier. Click Add Tier to add more Tiers.
**Example Tiered Pricing:**
```
Tier 1: Units 1–10 → $10/unit + $0 flat
Tier 2: Units 11–50 → $8/unit + $0 flat
Tier 3: Units 51–inf → $5/unit + $0 flat
```
Repeat for each currency you want to support. Stripe can auto-detect customer location and display the appropriate currency at checkout.
### 5.5 Add More Line Items (Optional)
Click Add Line Item to add additional charges. Common scenarios: base fee plus per-seat charges, monthly fee plus metered usage, or one-time setup fee plus recurring Subscription.
---
## Step 6: Save and Review Your Plan
### 6.1 Review Your Configuration
Before saving, verify your configuration is complete:
- **Plan Name**: Use customer-facing names (appears on checkout and invoices)
- **Line Items**: Correct pricing types configured with appropriate quantities
- **Pricing Intervals**: All billing intervals have prices configured
- **Currency Support**: All target currencies have prices for each interval
- **Tier Structures**: Breakpoints are logical, final tier ends with "inf"
### 6.2 Save the Plan
Click the Save Plan button. Your Plan is now ready to accept Subscriptions.
### 6.3 Create Additional Plans (Optional)
To offer multiple tiers, repeat steps 4–6 with different configurations (_eg_ Starter at \$29/month flat rate, Professional at \$99/month per-seat, Enterprise with custom tiered pricing).
---
## Step 7: Test Your First Checkout
> **Important** Test checkouts require the **business type** and **personal details** forms completed in Stripe Connect. Full onboarding is only needed for Live Mode.
### 7.1 Add Your Plan to Cart
At the bottom of your Plan, you'll find the Add to Cart form:
- **Currency**: Select from your configured currencies
- **Interval**: Choose the billing frequency (Month, Year, etc.)
- **[Owner](/docs/core-concepts#owner)** (required): An ID in your system for looking up Subscriptions—typically an organisation, team, or user ID
- **[Grantee](/docs/core-concepts#grantee)** (optional): The entity receiving feature access—typically a user ID. Can be assigned later for anonymous checkouts.
Click Add to Cart to add the Plan to your **[Cart](/docs/core-concepts#cart)**.
### 7.2 View Your Cart
Click Go to Cart to view your Cart. You can review the Plan, interval, currency, quantity, and price.
### 7.3 Complete Test Checkout
Click Checkout Cart to be redirected to Stripe's checkout page. Use [Stripe's test cards](https://docs.stripe.com/testing) (_eg_ `4242 4242 4242 4242`, any future expiry, any 3-digit CVC). Complete checkout and you'll be redirected to your Success URL.
You've created your first test Subscription.
---
## Step 8: View Your Subscription
### 8.1 Navigate to Subscriptions
In your sidebar, click Subscriptions to see your newly created test Subscription.
### 8.2 Explore Subscription Details
Click on the Subscription to view:
- **Status**: Active, trialling, past_due, cancelled, etc.
- **Plans Included**: All Plans attached to this Subscription
- **Line Items and Pricing**: Breakdown of all charges
- **Entitlements Granted**: Feature access permissions
- **Billing Cycle**: Current period, next renewal date
- **Payment History**: Past invoices and records
### 8.3 Test Subscription Management
Try these management actions:
- **Update Quantities**: Adjust seat count to see proration in action
- **Add Additional Plans**: Simulate purchasing add-ons
- **View Upcoming Invoice**: Preview the next charge
- **Cancel Subscription**: Test cancel at period end vs immediate cancellation
### 8.4 Check Entitlements
Verify the subscribed user has access to the Entitlements you configured.
**Via the Dashboard**
Navigate to Entitlement Check in your sidebar. Enter the Grantee ID and click Check Grantee to see all Entitlements your Grantee has access to.
**Via the API**
```bash
curl "https://api.salable.app/api/entitlements/check?granteeId=user_123" \
-H "Authorization: Bearer YOUR_PUBLISHABLE_KEY"
```
The response includes an array of Entitlements. Check whether the specific Entitlement you need is in the array before granting access to that feature.
---
## Step 9: Get Your API Keys
In your sidebar, click API Keys. You'll see two types:
- **Publishable Key**: Safe to use in frontend code
- **Secret Key**: Must be kept secure on your backend
Click the copy button and store them securely (_eg_ in your `.env`). Test Mode and Live Mode have separate keys.
> **Warning** Never expose your Secret Key publicly.
---
## Common Questions
### Can I change pricing for existing Subscriptions?
You can update prices and sync existing Subscriptions to new prices, or grandfather existing customers on old pricing. Use the **Sync Subscriptions** feature in the Product editor.
### How do I handle Plan upgrades and downgrades?
Salable handles this with [proration options](/docs/core-concepts#proration):
- **Charge on next invoice**: Prorated adjustment on next billing cycle
- **Charge immediately**: Instant invoice with prorated amounts
- **No refund, charge next**: Switch immediately, new charges start next cycle
### Can customers purchase multiple Plans at once?
Customers can add multiple Plans to their cart and checkout in a single transaction—useful for base Product plus add-ons.
> **Note** Each Plan can only be added to the cart once.
---
## Summary
You now have a connected Stripe account, a Product with Plans and Line Items, a working checkout flow, API keys, and your first test Subscription. Your billing infrastructure is ready.
---
### Core Concepts
Source: https://beta.salable.app/docs/core-concepts
# Core Concepts
## Foundational Concepts
### Test Mode vs Live Mode
Salable operates in two distinct environments to support safe development and testing. When you're working in Test Mode, you'll never process real money transactions. Instead, you'll use separate test API keys and Stripe's test cards for checkout, making it perfect for development and integration testing. Once you're ready to go live, you'll switch to Live Mode, which uses separate live API keys and processes real payments. Keep in mind that Live Mode requires you to complete Stripe's onboarding process before you can start accepting real payments in production.
---
## Pricing Hierarchy
### Product
Think of a Product as the top-level container representing what you're selling—whether that's a SaaS application or a service offering. It's the vehicle for defining your entire pricing model. Each Product contains one or more Plans and includes basic information like a name, description, and various settings that control how your pricing works.
### Plan
A Plan is a bundle of offerings, services, feature sets, or tiers set at the payment model you define. Think of it as a flexible container that brings together all the pricing components for a specific Subscription option—whether that's your Basic tier, Pro tier, an Analytics Add-on, or any other offering you want to sell.
Each Plan belongs to exactly one Product and can contain one or more Line Items with different pricing models. This is where Salable's real power shines: you can mix flat fees, per-seat charges, usage-based pricing, and one-time fees all within a single Plan. Want a base Subscription fee plus per-user pricing plus metered API calls? No problem—just add those Line Items to your Plan.
Plans are the purchasable entities in your app—when a customer subscribes, they're subscribing to a Plan. Line items within a Plan can be configured with different billing intervals and frequencies. When a customer adds a Plan to their cart, they specify an interval (like "month") and a value (like "1" for monthly or "3" for quarterly). If a currency is provided, Salable cherry-picks only the Line Items that match the requested interval, value, and currency combination. This flexibility means you can design sophisticated pricing models without creating dozens of separate Plans for each variation.
### Line Item
Line items are the individual pricing components that make up a Plan. You can combine multiple Line Items within a single Plan to create sophisticated pricing models. Each Line Item can be configured with different billing intervals and values, giving you tremendous flexibility in how you structure your pricing.
There are four types of Line Items you can use:
**Flat Rate** charges a fixed amount every billing cycle, regardless of usage. This is perfect for base Subscription fees—for example, a $29/month platform fee that every subscriber pays.
**Per-Seat** pricing multiplies the charge by the number of users, licenses, or seats. You might charge $10 per user per month, and you can set minimum and maximum quantities. Per-seat Line Items also support tiered pricing, so you can offer volume discounts as teams grow.
**Metered** pricing is usage-based—you bill customers based on what they actually use during the billing period. Think $0.01 per API call or $0.05 per GB of storage. These Line Items use slugs for tracking usage across billing periods.
**One-Time** charges happen just once and never recur. These are ideal for setup fees or installation charges—like a $99 onboarding fee that's billed only when someone first subscribes.
### Price
A Price represents the actual monetary value and configuration for a Line Item in a specific currency. Each Line Item can have Prices in multiple currencies, and each Price includes the amount, currency, and billing scheme details.
You can modify Prices at any time, but here's the important part: existing Subscriptions will continue using the Price version they started with. They won't automatically update to new pricing unless you explicitly move them. This gives you complete control over whether to grandfather existing customers on old pricing or migrate everyone to your new rates.
### Interval
The interval defines the billing frequency unit—day, week, month, or year. When combined with an interval value (a multiplier), you can create any billing frequency you need. For example, an interval of "month" with a value of 1 means monthly billing, but change that value to 3 and you have quarterly billing. Similarly, "week" with a value of 2 creates biweekly billing.
When customers add a Plan to their cart, they specify the interval and optionally the interval value (which defaults to 1 if not provided). Salable then cherry-picks only the Line Items from that Plan that match the requested combination. Intervals also affect how proration is calculated when Subscription changes occur mid-cycle.
### Currency
Currency represents the monetary unit for pricing and billing. How currency works depends on whether you specify it when creating a cart.
**When you provide a currency**: Salable cherry-picks only the Line Items from the Plan that have Prices in that specific currency and interval combination. This lets you create region-specific pricing models—for example, different Line Items or pricing structures for USD vs EUR customers.
**When you don't provide a currency (geolocation mode)**: Stripe automatically detects the customer's location and displays the appropriate currency. For this to work smoothly, every Line Item in the Plan must have the same default currency, and each Line Item should have Prices for every currency you want to support. If the defaults don't match, the checkout link will error. If a Line Item is missing a Price for a specific currency, that Line Item will fall back to its default currency. In this mode, all Line Items are used—no cherry-picking occurs.
---
## Access Control
### Owner
An owner is an identifier used to scope Subscriptions, carts, and usage records in Salable. This is typically a user ID for individual Subscriptions, or a team or organization ID for shared Subscriptions where multiple people need access to the same Subscription data.
The owner ID should be an identifier that anyone who needs to view Subscription data or increment usage meters will have access to. For example, in a team Subscription, all team members who might record usage would share the same owner ID (like a team or organization ID). Your application's RBAC determines who has permission to modify or cancel Subscriptions, and your business logic determines who is financially responsible—the owner ID is simply the scoping mechanism for organizing Subscription data in Salable.
You'll need to provide an owner when creating carts and Subscriptions, though you can update it later—which is useful when converting anonymous sessions to authenticated user IDs after signup. A single owner can have multiple Subscriptions and grantee groups, and you can filter entitlement checks by owner to scope results appropriately.
### Grantee
A grantee is any entity that receives access to features or entitlements through a Subscription. Grantees are identified by unique IDs from your system—a granteeId that's just a string you provide. These can represent users, teams, projects, boards, workspaces, or any other entity in your application that needs access to features.
Grantees can belong to one or more grantee groups, and they receive entitlements through these group memberships. You can optionally provide a name for display purposes, making it easier to manage your grantees in the dashboard. For example, you might have a user with ID `user_abc123` or a project with ID `project_xyz789`—both would be grantees in Salable.
### Group
A group is a collection of grantees that share access to features from a Subscription. This is how you implement team or organization Subscriptions in Salable. Each group has an owner (usually an organization or team lead) and contains zero or more grantees.
When you create a Subscription, you assign groups to Subscription items to grant access to all the grantees in that group. You can create groups before checkout (useful for pre-onboarding teams) or during the checkout process. A single owner can have multiple groups, which is perfect for organizations with different departments or teams.
Here's what makes groups powerful: once you've created a group for a team or organization, you can reuse that same group for future add-ons and additional Subscriptions. You don't need to recreate the group structure every time—just assign the existing group to new Plans as the team purchases more features.
One more important detail: a single grantee can belong to multiple groups, and they'll gain cumulative access from all their memberships. This makes it easy to handle scenarios like contractors working with multiple clients or employees who belong to cross-functional teams.
### Seat
A seat is essentially a license or slot for a grantee within a per-seat Line Item. The seat count is represented by the quantity on your per-seat Line Item and must be greater than or equal to the number of grantees in the assigned group.
You can define minimum and maximum limits for seats on your Line Item, giving you control over how teams can scale. Seat count and grantee count are managed independently, which means you can have "headroom"—more seats than current grantees—to allow for growth without immediately having to upgrade.
There's an important constraint to be aware of: if a grantee belongs to multiple groups with different Plans that have per-seat pricing, the lowest seat limit across all those Plans applies. This prevents under-provisioning of seats and ensures consistent access.
### Entitlement
An entitlement is a named permission or feature that you can check to control access in your application. Entitlements are named using lowercase snake_case (like `advanced_analytics` or `export_pdf`) and are attached to Plans rather than individual Line Items.
When a Subscription is created with a Plan, the entitlements from that Plan are inherited by the Subscription. To check if someone has access, you use their grantee ID. The pattern works like this: when a grantee is in a group assigned to a Subscription containing a Plan with an entitlement, that grantee has access to that entitlement. It's a chain of relationships that makes access control flexible and powerful.
## Tier Tags and Tier Sets
Tier tags are simply string "tags" belonging to Plans that can be used to **restrict Owners from purchasing one or more Plans that share the same tag**. Plans that share the same the tier tag belong in the same **tier set**, and as such an **Owner can only subscribe to/purchase one Plan in a tier set at a time**. Thus, tier tags and tier sets give you the ability to make your Plans **mutually exclusive purchases**.
What tier tags and tier sets prevent:
- Adding multiple Plans of one tier set to the same cart
- Adding Plans to a cart that belong to the same tier set as a Plan that's already subscribed to by the cart's owner
Note that **owners can still replace a Plan in a Subscription with another Plan belonging to the same Tier Set as they will still only be subscribed to one member of the tier set**.
You can add tier tags to your Plan upon Plan creation or when editing an existing Plan.
---
## Transaction Concepts
### Cart
A cart is a temporary container for Plans that an owner intends to purchase. Think of it as a shopping cart that gets converted into a Subscription after successful checkout. When you add the first item to a cart, you specify a billing interval. You can also optionally provide a currency when creating the cart.
If you provide a currency, Salable cherry-picks Line Items from added Plans that match both the interval and currency combination. If you don't provide a currency, Salable only matches by interval and uses Stripe's geolocation to detect the appropriate currency at checkout.
You can assign grantee groups to cart items before checkout, which is useful for pre-configuring team access. The owner can be updated later—for example, converting a session ID to a user ID after signup. Salable supports multiple active carts per owner, so customers can have different purchasing sessions going simultaneously.
### Cart Item
A cart item represents a single Plan within a cart. Each cart item references a specific Plan and includes a quantity (which matters for per-seat Line Items). It can optionally have a grantee group ID assigned to it.
### Checkout
Checkout converts your cart into a paid Subscription. When you're ready to complete a purchase, you generate a Stripe Checkout session URL that redirects your customer to Stripe's hosted payment page.
You can configure default checkout settings at the Product level, like success and cancel URLs. If you've set these defaults in your Product settings, they'll be used automatically. If you haven't provided Product defaults, you'll need to include any required configuration parameters when generating the checkout link.
Once payment succeeds, Salable creates the Subscription and any necessary grantee groups. At this point, all grantees in the assigned groups immediately gain access to the entitlements attached to their Plans. Checkout works for both authenticated users and anonymous sessions, which is useful for guest checkout flows where you assign the owner ID after the customer signs up.
### Subscription
A Subscription represents an active, recurring billing relationship between an owner and one or more Plans. Created after successful checkout, each Subscription contains one or more Subscription items (which are Plans) and automatically renews based on the billing interval.
Subscriptions aren't static—you can modify them after creation by adding or removing Plans, changing quantities, or updating other settings. When it's time to end a Subscription, you can cancel it either immediately (with proration handling for any unused time) or schedule the cancellation for the end of the current billing period.
### Subscription Item
A Subscription item represents a single Plan within a Subscription. Each item links to the Plan and its Line Items, includes quantities for per-seat pricing, and may have a grantee group assigned to it. When a group is assigned, all grantees in that group receive the entitlements from the Plan. You can individually modify or remove Subscription items without affecting the entire Subscription.
---
## Usage Tracking
### Metered Line Item
Metered Line Items let you charge customers based on what they actually use. Throughout the billing period, you record usage via API calls using a unique slug identifier. At the end of each period, Salable automatically calculates and bills the charges.
Here's what makes meters elegant: you can use the same slug across multiple Plans, keeping your code simple. When recording usage, you just increment against the slug name—like "photo_generation"—regardless of which Plan the user is on. Salable figures out their Plan and bills at the appropriate rate. So one Plan might charge $0.10 per photo while another charges $0.05 per photo, but you're always incrementing the same slug.
When customers change Plans, any outstanding metered usage is invoiced immediately, and new counters start fresh at zero. Plans can have multiple metered Line Items, so you could track photos, videos, and API calls all within the same Plan. Metered Line Items support per-unit, tiered, and volume pricing schemes.
### Meter Slug
A meter slug is the unique identifier used to track usage for metered Line Items. It follows lowercase snake_case format, like `api_calls` or `storage_gb`. When you record usage via the API, you reference this meter slug to increment the counter.
The key advantage is reusability: you can use the same meter slug across multiple Plans. This ensures there's one usage counter per owner per meter slug, preventing double-billing. If you have "photo generation" on both Basic and Pro Plans, use the same meter slug (`photo_generations`) on both. Your code increments one counter, but billing happens at different rates depending on which Plan the customer has.
### Usage Record
A usage record tracks metered usage for an owner during a billing period. When you record usage for the first time in a period, Salable automatically creates a usage record. Throughout the period, this record tracks cumulative usage as you continue to increment the counter.
Usage records move through states during their lifecycle. When usage is actively being tracked during the billing period, the record has `current` status (also referred to as `recorded` status in some contexts). At the end of the billing period (or when a Plan changes), the usage record is finalized and its state changes to `final`. The accumulated usage is then billed. There's one usage record per owner per meter slug per period, keeping things organized and preventing any confusion about what's been billed.
---
## Billing Concepts
### Proration
Proration handles the financial adjustments when Subscriptions change mid-cycle. There are three approaches you can take:
**Charge on Next Invoice** is the most common approach. It refunds any unused time from the old Plan and starts billing for the new Plan at the next cycle. This keeps things clean with minimal immediate financial impact.
**Charge Immediately** refunds the unused time from the old Plan and bills for the new Plan right away, creating an instant invoice. This is useful when you want to settle everything immediately rather than waiting for the next billing cycle.
**No Refund, Charge Next** switches to the new Plan immediately but doesn't refund any unused time. The new Plan starts billing at the next cycle. This essentially gives the customer the benefit of the remaining time on their old Plan while moving to the new one.
### Billing Cycle
The billing cycle is the time period between recurring charges for a Subscription. Its length is determined by the Plan's interval—monthly, yearly, or whatever interval you've configured. The cycle starts on the Subscription creation date (called the billing anchor), and usage for metered items resets at the start of each cycle. All Plans within a Subscription share the same billing cycle.
### Invoice
An invoice is the document showing all charges for a billing period. Salable generates invoices automatically at the end of each cycle, including flat fees, per-seat charges, and metered usage all in one place. You can preview upcoming invoices before the period ends, and once generated, invoices are downloadable as PDFs. Paid invoices are immutable—they can't be changed after payment.
### Billing Anchor
The billing anchor is the date when a Subscription was created, and it determines all future billing dates. This sets the recurring charge date and is used to calculate proration when Subscriptions change. The billing anchor remains consistent across Plan changes. For example, if you create a Subscription on January 15th, it will bill on the 15th of each month going forward.
---
## Operational Concepts
### Cancellation
Cancellation terminates a Subscription, and you have two options for how this happens.
**Immediate cancellation** ends the Subscription right away. Metered usage is finalized and billed, access is revoked immediately, and a final invoice may be generated. This is clean and final.
**End of period cancellation** marks the Subscription for cancellation but lets it continue until the current billing period ends. This can be reversed before the period ends, and there are no immediate access changes—the customer keeps their access until they've used up what they paid for.
### Sync to Latest Price
When you update your pricing, you might want to move existing Subscriptions to the new Prices. That's what syncing to latest Price does. You have a choice: grandfather existing customers by not syncing them (they stay on old pricing), or migrate everyone to the new pricing by syncing them. When you sync, proration rules apply during the transition to handle any mid-cycle adjustments.
### Webhook
Webhooks are HTTP callbacks that Salable sends to your application when events occur. They notify your app of Subscription changes, usage updates, and payment events. Each webhook includes the event type and full payload, and requires signature verification using HMAC to ensure authenticity.
Salable will retry failed webhooks up to 10 times with exponential backoff, and each attempt has a 15-second timeout. Common events include Subscription created, updated, and cancelled; usage recorded and finalized; receipt created; and owner updates.
For a complete guide to configuring webhook destinations, implementing handlers, and monitoring deliveries, see the [Webhooks guide](/docs/webhooks).
---
## Special Patterns
### Anonymous to Authenticated Conversion
This pattern lets you start a cart with a session ID and update it to a user ID after signup. Here's how it works: You create a cart with an owner like `"session_abc123"`, let the user add Plans to their cart, and redirect them to checkout. After payment completes, the user signs up, and you update the owner to their actual user ID like `"user_xyz789"`. This enables guest checkout flows that convert to authenticated accounts seamlessly.
### Pre-Purchase Team Setup
You can add grantees to a group before completing checkout, which offers several benefits. You can invite team members before subscribing, show everyone who will get access, and validate that seat counts match team size. The flow is straightforward: create a grantee group, add grantees to it, create a cart item with the group assigned, set the quantity to match the group size (or more to allow room for growth), and proceed to checkout. Once payment succeeds, everyone in the group immediately has access.
### Cross-Plan Metering
This pattern uses the same meter slug across multiple Plans to maintain a single usage counter. For example, your Basic Plan might charge $0.10 per photo generation using the slug `photo_generations`, while your Pro Plan charges $0.05 per photo generation using the same slug. Usage is counted once in your code—you just increment `photo_generations`—but it's billed at the rate of whichever Plan the customer has. This keeps your implementation simple while giving you pricing flexibility across tiers.
---
## Quick Reference
### Hierarchy Overview
```
Organization
└─ Product
└─ Plan (specific interval + currency)
└─ Line Item (flat/seat/metered/one-time)
└─ Price (amount in currency)
```
### Access Flow
```
Subscription Item → Assigned Grantee Group → Contains Grantees
→ Plan → Entitlements → Grantees Have Access
```
### Checkout Flow
```
Cart → Cart Items (Plans + Groups) → Checkout → Payment
→ Subscription Created → Grantee Groups Assigned → Access Granted
```
### Billing Cycle
```
Start Date → Usage Recording → End of Period → Finalize Usage
→ Generate Invoice → Process Payment → New Period Begins
```
---
## Next Steps
Now that you understand the core concepts, explore these guides to implement specific patterns:
- **Getting Started Guide**: Build your first Product and Plan
- **Understanding Entitlements**: Implement feature gating
- **Grantee Groups**: Set up team Subscriptions
- **Webhooks**: Configure real-time event notifications
- **Caching Strategies**: Optimize entitlement checks in production
---
### Products & Pricing
Source: https://beta.salable.app/docs/products-and-pricing
# Products & Pricing
## Overview
**[Products](/docs/core-concepts#product)** and pricing form the foundation of your subscription business in Salable. A Product represents what you're selling—whether that's a SaaS platform, a specific feature set, or an add-on service. Products contain **[Plans](/docs/core-concepts#plan)** that define different pricing Tiers or options, and Plans contain **[Line Items](/docs/core-concepts#line-item)** that determine what customers pay and how they're charged.
This hierarchy enables unlimited payment model configurations. From simple flat-rate subscriptions, per-seat pricing with volume discounts, usage-based metered billing, or any intricate combination your SaaS app needs. Additionally, you can offer the same Product at different prices in different currencies, with different billing intervals, and with different feature access through **[Entitlements](/docs/core-concepts#entitlement)**.
## Understanding the Hierarchy
1. **Product** is the top-level. It represents what you're selling and contains one or more Plans. A Product might be your core SaaS platform, a specific add-on service, or a bundle of features. Products have settings that apply to all Plans within them, like checkout URLs, tax collection preferences, and trial period configurations.
2. **Plan** is what customers purchase. Plans are added to the Cart and checked out as a single unit—customers pay for all Line Items within a Plan together. Plans define which Entitlements customers receive (controlling feature access) and contain one or more Line Items that determine the actual charges.
3. **Line Item** defines a specific charge within a Plan—these are the individual prices that appear on invoices. Line Items are not sold individually; they're bundled together within a Plan. A Plan might have multiple Line Items—for example, a base subscription fee, a per-seat charge, and usage-based billing for API calls. Each Line Item has a type (flat rate, per-seat, or metered), an interval (one-time or recurring), and a billing scheme (per-unit or tiered).
4. **Price** represents the Line Item's pricing at a specific billing interval. A single Line Item can have multiple Prices—one for monthly billing, one for yearly billing, and so on. Each Price contains Currencies for different markets.
5. **Currency** defines the actual pricing in a specific currency. A Price can have multiple Currencies (USD, GBP, EUR), with one marked as the default. This enables global pricing without creating duplicate Line Items.
6. **Tier** (optional) defines pricing breakpoints for tiered billing schemes. Tiers let you charge different amounts based on quantity or usage levels, enabling volume discounts or graduated pricing structures.
## Product Configuration
### Creating a Product
Products can be created on the Salale dashboard and the Salable API.
> **Note** Before creating Products, you must create a Stripe Connect account by setting up a Payment Integration. Products and Plans can be created with minimal Stripe Connect setup. For test mode checkout links, you must complete the business type and personal details forms in Stripe Connect's onboarding. For Live Mode checkout links, you need full onboarding with **Active** status.
Navigate to **Products** in your sidebar. Enter a name for your Product in the Product Name field that clearly describes what you're selling and click Create Product. You should see your new Product in the list below, click the Edit Product button (button with the pencil icon) to continue setting up your Product.
### Product Settings
Product settings define defaults that apply to all Plans within the Product.
- **Checkout URLs**
Specifies where customers are redirected after completing checkout. The `successUrl` is where they redirected after successful payment, while the `cancelUrl` is where they return if they abandon the checkout process. These URLs can include query parameters to pass information back to your application, allowing you to trigger onboarding flows or analytics tracking based on the checkout result.
- **Allow Promo Codes**
The **allowPromoCodes** setting enables customers to enter discount codes during checkout. When enabled, a promo code field appears on the Stripe checkout page where customers can apply codes you've configured in your Stripe dashboard.
- **Automatic Tax**
The **automaticTax** setting enables [Stripe Tax](https://stripe.com/tax) for automatic tax calculation based on customer location. When enabled, Stripe determines the correct tax rate and applies it to invoices. This requires collecting the customer's billing address to determine their tax jurisdiction.
- **Address Collection**
The **collectBillingAddress** setting determines whether billing address fields are shown at checkout. This is required if you're using automatic tax calculation, as Stripe needs the customer's location to determine applicable tax rates. The **collectShippingAddress** setting adds shipping address fields to the checkout flow if you're selling physical products that require delivery.
- **Card Pre-fill Preference**
The **cardPrefillPreference** setting determines how payment methods are saved and reused. Set it to `none` to always show an empty payment form, `choice` to let customers decide whether to save their card, or `always` to save payment methods for future purchases.
- **Past Due Entitlements**
The **pastDueEntitlements** setting controls feature access during payment issues. When set to true, customers retain access to Entitlements even when their Subscription payment fails, and the Subscription enters a past-due state. This maintains access during Stripe's automatic retry period, reducing disruption for customers with temporary payment issues. When set to false, access is immediately revoked when payments fail.
- **Trial Periods**
The **trialPeriodDays** setting is configured at the Plan level. See [Plan Properties](#plan-properties) for details.
> **Note** Product settings can be overridden at checkout time.
> **Important**: Automatic tax will only work if you have opted in to Stripe Tax in your Stripe account settings. You must enable and configure Stripe Tax in your Stripe Dashboard before using this setting in Salable.
## Plans
Plans represent different pricing tiers, add-ons, or options within a Product. They can be Subscription tiers like Basic, Pro, and Enterprise, or they can represent add-ons and plugins that customers purchase alongside or in addition to a main Plan. Each Plan has different pricing, features, and Line Item configurations.
### Creating Plans
Plans are created within the Product editor in the dashboard. Navigate to the Edit Product page for your Product, scroll to the Plans section, and click **Create Plan**. Enter a nsme for your Plan in the Plan Name field that clearly identifies the tier or option you want your Plan to represent, optionally set a Trial Period in days, and select any Entitlements that customers who purchase this Plan should receive.
The Plan is saved only after you've configured at least one Line Item with pricing.
### Plan Properties
- **Name** identifies the Plan to customers. It is recommended to use clear, descriptive names such as "Professional Plan" or "Enterprise" rather than internal codes. This name appears in checkout flows, invoices, and customer-facing areas.
- **Trial period** gives customers free access for a specified number of days before charging them. Trial periods must be between 1 and 730 days. During the trial, customers have full access to Entitlements but aren't charged until the trial ends. If they cancel during the trial, they won't be charged.
- **Entitlements** define which features customers on this Plan can access. When configuring the Plan, use the Entitlements typeahead input to either search for an existing Entitlement or create a new one inline by typing the Entitlement name and clicking Create. Customers who subscribe receive all selected Entitlements, which you can check in your application to gate features.
- **[Tier Tags](/docs/core-concepts#tier-tags-and-tier-sets)** make Plans mutually exclusive by grouping them into tier sets. When you assign the same tier tag to multiple Plans, an Owner can only subscribe to one of those Plans at a time. To configure tier tags, add them when creating a Plan or when editing an existing Plan. Enter your desired tier tag in the Tier Tag field.
## Line Items
Line Items define the actual charges within a Plan—what customers pay, how often, and how the amount is calculated.
### Naming
Line Item names appear on Stripe invoices, receipts, and checkout pages. Use clear, customer-facing descriptions—"Platform Subscription" rather than "base_fee", "Additional Users" rather than non-descript names such as "per_user".
### Line Item Types
- **Flat Rate** charges a fixed amount per billing cycle, regardless of usage or team size. For example, a \$29/month subscription fee. flat rate Line Items always have a quantity of one.
- **Per Seat** charges based on the number of seats (users, licenses, or units). The price is multiplied by the quantity. For example, \$10 per user per month, where a team with five users pays \$50/month. Per-seat pricing can use simple per-unit billing or tiered pricing with volume discounts. Only one per-seat Line Item is allowed per Plan to avoid ambiguity about seat counting.
- **Metered** charges based on actual usage during the billing period. Customers are billed for what they consume—such as API calls, storage, or processing time. Usage is tracked throughout the billing cycle and invoiced at the end. Metered Line Items require a meter to track usage.
### Interval
Line Items have an interval that determines when they're charged.
- **Recurring** Line Items repeat every billing cycle. The charge appears on every invoice at the configured interval (day, week, month, or year). This is used for Subscription fees, per-seat charges, and recurring metered billing. The interval count proeprty lets you create custom billing periods—for example, an interval of "week" with a count of two creates biweekly billing, or "month" with a count of three creates quarterly billing.
- **One-off** Line Items charge only once, at the start of the Subscription. This is perfect for setup fees, onboarding charges, or one-time purchases.
### Billing Schemes
The billing scheme determines how the Line Item Price is calculated.
- **Per Unit** applies a fixed price per unit. If you set a unit amount of \$10 and the customer purchases five units, they pay \$50. Works for flat Rate, per-seat, and metered Line Items.
- **Flat Rate** (as a billing scheme) charges a single fixed amount regardless of quantity. Typically used when the price type is flat rate with a quantity of one, but can also apply to per-seat items where you want a flat fee regardless of seat count.
- **Tiered** applies different pricing based on quantity or usage levels. Tiers define breakpoints where pricing changes. For example, units 1–10 might cost \$10 each, units 11–50 cost \$8 each, and units 51+ cost \$5 each. Tiered billing supports both volume and graduated modes (explained in the next section).
### Quantity Controls
Line Items have quantity constraints that determine valid purchase amounts.
- **Minimum quantity** sets the minimum amount of units customers must purchase. For flat rate items, this is typically zero or one. For per-seat items, you may want to set a minimum of two to enforce team pricing.
- **Maximum quantity** sets the maximum amount of units customers can purchase. The maximum quantity enforces Plan limits and prevents over-purchase.
- **Default quantity** is the pre-filled amount that customers see when they add the Plan to their Cart. For flat rate items, this is usually one. For per-seat items, you may want to default to a resonable amount (_eg_ five users etc) to give customers a starting point.
- **Allow changing quantities** determines whether customers can adjust the quantity at checkout or when managing their Subscription. Enable this for flexible per-seat pricing, disable it to lock quantities to your configured values.
## Tiered Pricing
Tiered pricing lets you charge different amounts based on quantity or usage levels, enabling volume discounts and encouraging higher-tier purchases.
### Tier Modes
Tiered billing schemes have two modes that determine how prices are calculated across Tiers.
- **Graduated** pricing charges different rates for units within each Tier. Think of it like progressive income tax—the first 100 units cost \$10 each, the next 100 cost \$8 each, and so on. Each unit is priced according to its Tier.
**Example of Graduated Pricing:**
```
Tier 1: Units 1–100 at $10/unit
Tier 2: Units 101–500 at $8/unit
Tier 3: Units 501+ at $5/unit
Customer purchases 600 units:
- First 100 units: 100 × $10 = $1,000
- Next 400 units: 400 × $8 = $3,200
- Final 100 units: 100 × $5 = $500
Total: $4,700
```
- **Volume** pricing applies a single rate to all units based on the total quantity. When you cross into a new Tier, all units are priced at that Tier's rate, not just the units in that Tier.
**Example of Volume Pricing:**
```
Tier 1: 1–100 units at $10/unit
Tier 2: 101–500 units at $8/unit
Tier 3: 501+ units at $5/unit
Customer purchases 150 units:
- All 150 units: 150 × $8 = $1,200
(All units use the Tier 2 rate of $8)
Customer purchases 600 units:
- All 600 units: 600 × $5 = $3,000
(All units use the Tier 3 rate of $5)
```
### Configuring Tiers
Each Tier has three components that define its pricing.
- **Up To** sets the upper limit of the Tier. This is a number representing the last unit in the Tier, or `inf` for the final Tier that has no upper limit. For example, a Tier with "up to 100" includes units 1–100. The Tier after it would start at 101.
- **Unit Amount** is the Price per unit within this Tier. This amount applies to each unit (in graduated mode) or to all units if the total falls in this Tier (in volume mode).
- **Flat Amount** is an optional base fee charged when entering this Tier. This amount is added once if the customer's usage reaches this Tier. For example, you might charge a \$50 flat fee plus \$5 per unit for the top Tier. Flat amounts are often used to cover fixed costs at higher usage levels.
### Tier Configuration Example
Navigate to your Line Item configuration and select **Tiered** as the billing scheme. Choose **Graduated** as the Tier mode. Then configure your Tiers:
Example:
```
Tier 1:
- Up To: 10
- Unit Amount: 10.00
- Flat Amount: 0 (or leave empty)
Tier 2:
- Up To: 50
- Unit Amount: 8.00
- Flat Amount: 0
Tier 3:
- Up To: inf
- Unit Amount: 5.00
- Flat Amount: 0
```
## Prices and Currencies
Prices define how much a Line Item costs at different billing intervals and in different currencies.
### Billing Intervals
A single Line Item can have multiple Prices for different billing intervals. This lets customers choose how often they want to be billed without you creating duplicate Line Items.
Create a Price for each interval you want to support: **Day**, **Week**, **Month**, or **Year**. For example, add a monthly Price at \$29/month and a yearly Price at \$290/year (offering a 17% discount).
### Multi-Currency Support
Each Price can have multiple Currencies, so you can sell globally without duplicating your Product structure.
- **Default currency** is set using the default button on each Currency in the Price form. This is the currency Stripe uses when determining Prices based on geolocation if you omit currency in the Cart. All Line Items in a Product must share the same default currency for geolocation to work correctly.
- **Additional currencies** let you expand into new markets. Add Currencies for each market you want to serve. You can set different Prices in different currencies—Prices don't need to be simple conversions. For example, you might charge \$29/month in USD, £24/month in GBP (not just a conversion), and €27/month in EUR, adjusting for local market conditions and purchasing power.
- **Configuring currencies** in the dashboard is done in the Price configuration. After selecting an interval, click Add Currency and choose from the dropdown. Enter the unit amount and add as many currencies as you need to support.
For tiered pricing, configure Tiers separately for each currency. While Tier breakpoints (the "up to" values) are typically the same across currencies, you might adjust unit amounts and flat amounts for different markets.
### Example Price Configuration
A per-seat Line Item with monthly and yearly billing in multiple currencies:
```
Line Item: User Seats
- Price Type: Per Seat
- Billing Scheme: Per Unit
- Min Quantity: 1
- Max Quantity: 100
- Default Quantity: 5
Monthly Price:
- Interval: Month
- Currency: USD
- Unit Amount: 10.00
- Currency: GBP
- Unit Amount: 8.00
- Currency: EUR
- Unit Amount: 9.00
Yearly Price:
- Interval: Year
- Currency: USD
- Unit Amount: 100.00
- Currency: GBP
- Unit Amount: 80.00
- Currency: EUR
- Unit Amount: 90.00
```
## Combining Multiple Line Items
Plans can include multiple Line Items that work together to create sophisticated pricing models.
### Common Combinations
- **Base fee + Per-seat** is a popular model where customers pay a fixed platform fee plus a per-user charge. For example, \$50/month base fee plus \$10/user/month. This ensures you cover fixed costs while scaling revenue with team size.
- **Flat rate + Metered** combines predictable recurring revenue with usage-based charges. For example, \$99/month base Subscription plus \$0.01 per API call. Customers get a base service level included and pay for additional consumption.
- **Per-seat + Metered** charges for both team size and usage. For example, \$20/user/month plus \$0.50 per transaction processed. This works well when costs scale with both dimensions.
- **Multiple metered items** track different types of usage separately. For example, you might charge \$0.02 per image processed, \$0.01 per API call, and \$0.10 per GB of storage used. Each has its own meter and pricing.
- **One-time setup + Recurring** charges customers once for onboarding or setup, then bills recurring fees. For example, a \$500 setup fee (one-off) plus \$199/month (recurring). The setup fee appears only on the first invoice.
## Troubleshooting
### Cannot Add Per-Seat Line Item
If you're getting an error adding a per-seat Line Item, check if the Plan already has one. If you need different per-seat pricing, use tiered pricing within the single per-seat Line Item rather than creating multiple items.
### Tiered Pricing Validation Errors
Tiers must be configured in ascending order without gaps. Each Tier's starting point is automatically calculated from the previous Tier's "up to" value plus one. The final Tier must have "up to: inf" to handle all quantities beyond the previous Tier.
Unit amounts cannot be negative. If you want to offer discounts at higher Tiers, reduce the unit amount for higher Tiers compared to lower Tiers—don't use negative numbers.
### Currency Amount Format
Salable accepts Prices with or without decimals. You can enter 29 or 29.00 for \$29.00—both are valid.
For zero-decimal currencies (like JPY, KRW), you must enter whole numbers without decimal places. For example, 1000 JPY must be entered as 1000. Entering 1000.00 will cause an error.
### Default Currency Mismatch
If you're using Cart geolocation (omitting currency when creating Carts), all Line Items across all Plans in your Product must share the same default currency. Check each Line Item's default Currency. If Plan A defaults to USD and Plan B defaults to GBP, you must either standardise defaults or require explicit currency selection in Carts.
## Summary
Salable's pricing system lets you build everything from simple flat-rate Subscriptions to complex multi-dimensional pricing with volume discounts and usage-based charges. Use flat rate for fixed charges, per-seat for team-based pricing, and metered for usage-based billing. Support global markets with multi-currency pricing and tiered pricing with graduated or volume modes.
For more on how the Entitlements control feature access, see the [Understanding Entitlements guide](/docs/understanding-entitlements). For managing team access and seats, see [Grantees & Groups](/docs/grantee-groups). For checkout flows and Cart management, see [Cart & Checkout](/docs/cart-and-checkout).
---
### Understanding Entitlements
Source: https://beta.salable.app/docs/understanding-entitlements
# Understanding Entitlements
Every SaaS app faces a challenge: keeping feature access synchronised with billing. When a customer subscribes, they need immediate access. When they upgrade, new features should unlock instantly. When a payment fails or a Subscription ends, access must be revoked.
**[Entitlements](/docs/core-concepts#entitlement)** are Salable's answer to this problem. An Entitlement is a string identifier—such as `analytics` or `sso`—that grants access to a feature in your application. You define Entitlements for each feature you want to gate, attach them to **[Plans](/docs/core-concepts#plan)**, and then check whether a user has them before granting access.
You can focus on building features and defining which Plans include them—Salable handles the complex logic of tracking Subscription status, managing grace periods, and ensuring access stays perfectly synchronised with billing.
## How Entitlements Work
**[Subscriptions](/docs/core-concepts#subscription)** have a **[Group](/docs/core-concepts#group)** associated with them. All **[Grantees](/docs/core-concepts#grantee)** in a Group receive the Entitlements from that Subscription's Plan. A Grantee can belong to multiple Groups and receive Entitlements from each.
> **Note** Only Subscriptions to Plans with a per-seat Line Item have Groups attached.
Example:
```
Plan "Pro"
├─ Entitlements: ['analytics', 'api_access', 'export_csv']
└─ Subscription (active)
└─ Group "Acme Corp"
└─ Grantee "user_123"
└─ Has: analytics, api_access, export_csv
```
### Lifecycle in Action
Salable automatically adjusts access when Subscriptions change:
**Subscription created or renewed:** Entitlements are immediately available. When a Pro Subscription is created, `analytics` and `api_access` are granted instantly.
**Subscription upgraded:** New Entitlements are added. When a Subscription upgrades from Pro to Enterprise, `sso` and `priority_support` are granted immediately.
**Subscription downgrade:** Entitlements are removed. When a Subscription downgrades from Enterprise to Pro, access to `sso` and `priority_support` is revoked.
**Payment fails (past_due):** You can control whether access continues. If "Return Entitlements While Past Due" is enabled on the Product, access continues while Stripe attempts to recover payment. If disabled, access is revoked immediately.
**Subscription cancelled:** Entitlements are revoked. When the Subscription ends, access stops—no manual intervention required.
## Attaching Entitlements to Plans
You can create and manage Entitlements while creating or managing your Plans. In the Product management view, each Plan has a Select Entitlements field that lets you search existing Entitlements or create new ones—type a name to filter, or enter a new name to create it. Once created, an Entitlement can be reused across any of your Plans.
### Naming Your Entitlements
Entitlement names must use lowercase letters with underscores (snake_case). No spaces, hyphens, or special characters.
**Valid:** `api_access`, `advanced_features`, `priority_support`
**Invalid:** `API_Access` (uppercase), `api-access` (hyphens), `api access` (spaces), `api_access_` (trailing underscore)
There are two common conventions for selecting Entitlement names:
**Feature-based naming** ties Entitlements to specific capabilities: `api_access`, `export_data`, `custom_reports`. This offers granular control—you can mix and match Entitlements across Plans, create bespoke Subscriptions for specific customers, and easily move features between tiers as your pricing evolves.
**Tier-based naming** bundles features by plan level: `basic_features`, `pro_features`, `enterprise_features`. Although convenient and intuitive at first this convention and cause limitations if you later need to sell individual features separately or create custom arrangements for enterprise customers.
You can combine both approaches based on your needs.
## Checking Entitlements
If a Grantee has access to multiple Subscriptions—say a base plan plus an analytics add-on—they receive Entitlements from all of them. Entitlements are returned from Subscriptions that are `active`, `trialing`, or optionally `past_due` if you've enabled "Return Entitlements While Past Due" on the Product.
### Via the Dashboard
To verify a user's access, navigate to Entitlement Check in the dashboard. Enter a Grantee ID and click Check Grantee to see their current Entitlements.
### Via the API
**Endpoint:** `GET /api/entitlements/check`
**Query Parameters:**
- `granteeId` (required): The Grantee to check
**Example Request:**
```bash
GET /api/entitlements/check?granteeId=user_alice
```
**Example Response:**
```json
{
"entitlements": [
{ "type": "entitlement", "value": "api_access", "expiryDate": "2026-01-15T10:00:00Z" },
{ "type": "entitlement", "value": "advanced_analytics", "expiryDate": "2026-01-15T10:00:00Z" }
],
"signature": "a3f5b8c2d9e1..."
}
```
The `value` is the Entitlement name. The `expiryDate` indicates when the current billing period ends—if an Entitlement is returned, it's active, and your application should grant access. If the expiry date is in the past, the Subscription is in a grace period. If a Grantee has multiple Subscriptions providing the same Entitlement, Salable returns the expiry date furthest in the future. The `signature` can be used to verify that the response hasn't been tampered with.
For perpetual subscriptions (one-off purchases with no recurring billing), `expiryDate` will be `null`, indicating the entitlement never expires.
### Subscription Status Reference
| Status | Returned? | Notes |
| -------------------- | ----------- | ---------------------------------------------------------------------------------------------------------- |
| `active` | Yes | Normal active Subscription |
| `trialing` | Yes | During trial period |
| `past_due` | Conditional | Only if **[Product](/docs/core-concepts#product)** setting "Return Entitlements While Past Due" is enabled |
| `canceled` | No | Subscription has ended |
| `incomplete` | No | Payment not completed |
| `incomplete_expired` | No | Payment attempt expired |
| `unpaid` | No | Failed to collect payment |
## Implementing Access Control
Authorise your API endpoints by returning a 403 when the required Entitlement is missing:
```javascript
app.get('/api/advanced-analytics', async (req, res) => {
const { entitlements } = await getEntitlements(req.user.id);
const hasAccess = entitlements.some(ent => ent.value === 'advanced_analytics');
if (!hasAccess) {
return res.status(403).json({ error: 'This feature requires a Pro Subscription' });
}
res.json({ data: getAdvancedAnalytics() });
});
```
On your frontend, use Entitlements to control what users see—hiding unavailable features or showing upgrade prompts:
```javascript
function AdvancedAnalytics({ entitlements }) {
const hasAccess = entitlements.some(ent => ent.value === 'advanced_analytics');
if (!hasAccess) {
return ;
}
return ;
}
// Conditionally render based on entitlements
{
entitlements.some(ent => ent.value === 'export_data') && ;
}
```
> **Important** Frontend checks may improve user experience but can be bypassed. For sensitive features, always enforce access on your backend.
## Modifying Entitlements on Active Plans
When you add or remove Entitlements from a Plan with active Subscriptions, changes take effect on the next Entitlement check.
**Adding an Entitlement to a Plan:** All existing subscribers immediately gain access to the new feature. This is useful when you're launching a new capability and want to roll it out to current customers.
**Removing an Entitlement from a Plan:** All existing subscribers immediately lose access. Make sure to communicate changes to your customers before removing Entitlements they're actively using.
## Advanced Topics
### Entitlement Signatures
Every Entitlement check response includes a cryptographic signature that proves the response came from Salable. This is useful when passing Entitlement data from your backend to your frontend—you can verify the data hasn't been tampered with.
```javascript
const crypto = require('crypto');
function verifyEntitlements(entitlements, signature, publicKey) {
const data = JSON.stringify(entitlements);
const verify = crypto.createVerify('sha256');
verify.write(data);
verify.end();
return verify.verify(publicKey, signature, 'hex');
}
```
### Filtering by Owner
The `owner` query parameter scopes Entitlement checks to Subscriptions owned by a specific Owner. This is useful when a Grantee belongs to multiple Groups with different Subscriptions—for example, a user who works with several teams.
```bash
# Check user's access within Team A
GET /api/entitlements/check?granteeId=user_john&owner=team_a
# Check user's access within Team B
GET /api/entitlements/check?granteeId=user_john&owner=team_b
```
By including the `owner` parameter, the response also includes metered Line Item slugs, letting you check both feature access and usage permissions in a single call.
## Troubleshooting
### Entitlement Not Returned
In the case that an expected Entitlement doesn't appear:
1. Verify the Entitlement is attached to the Plan
2. Confirm the Grantee is in a Group with an active Subscription to that Plan
3. Check the Subscription status is `active` or `trialing`
4. Verify the Grantee is in the correct Group
### Entitlement Check Returns 404
A 404 indicates that the Grantee doesn't exist. Create the respective Grantee and add them to a Group with an active Subscription.
---
### Grantees & Groups
Source: https://beta.salable.app/docs/grantee-groups
# Grantees & Groups
## Overview
Most subscription systems assume one subscription equals one user. But real applications are more complex—teams share access, organisations manage multiple departments, and a single subscription often needs to grant access to many people. Building this yourself means tracking group memberships, managing seat limits, and keeping access in sync as teams grow and change.
The Grantee and Group system solves this. You can manage Subscriptions for entire teams rather than just individuals, add users to Groups before they even purchase, and dynamically allocate seats and manage access. A single Subscription can control access for an entire team, and Grantees can represent any entity in your system—users, boards, workspaces, projects, or whatever makes sense for your application.
## Terminology
### Owners
An **[Owner](/docs/core-concepts#owner)** is an identifier used to scope Subscriptions, Carts, and metered usage in Salable. In your application, this typically represents a user ID for individual Subscriptions, or a company, organisation, team, or project ID for shared Subscriptions where multiple people need access to the same Subscription data.
The Owner ID should be an identifier that anyone who needs to view Subscription data or increment usage meters will have access to. For example, in a team Subscription, all team members who might record usage would share the same Owner ID (like a team or organisation ID). Your application's RBAC determines who has permission to modify or cancel Subscriptions, and your business logic determines who is financially responsible—the Owner ID is simply the scoping mechanism for organising Subscription data in Salable.
Each Owner can own multiple Subscriptions and Grantee Groups. You'll need to provide an Owner when creating Carts and Subscriptions.
### Grantees
**[Grantees](/docs/core-concepts#grantee)** are individual identities that receive access to your application's features. They can represent individual users, boards, workspaces, projects, API keys, service accounts, or any other identifiable entity in your system.
Each Grantee is identified by a `granteeId` that you provide. You can optionally include a display name for easier management in the dashboard. Grantees can belong to multiple Groups, and their access is checked via the `/api/entitlements/check` endpoint.
### Groups
**[Groups](/docs/core-concepts#group)** are collections of Grantees under a single Owner. They serve as the link between Subscriptions and individual access. Each Group belongs to one Owner and can contain multiple Grantees (members). When you create Subscriptions, you attach Plans to Groups rather than individual Grantees. Groups can have an optional name for identification, and one Owner can have multiple Groups—perfect for organisations with different departments or teams.
### Memberships
Memberships are the join records that link Grantees to Groups. Each Membership links one Grantee to one Group, and they're managed automatically when you add or remove Grantees from Groups.
## The Relationship Model
```
Owner (pays for subscription)
└─ Has many Groups
└─ Has many Grantees (via Memberships)
└─ Has many Subscription Plans (with seats)
Subscription
└─ Belongs to Owner
└─ Has many Subscription Plans
└─ Each plan is linked to a Group
└─ Each plan has a seat count
```
## How Access Works
When you check if a Grantee has access to a feature, Salable follows a straightforward path. First, the system looks up the Grantee by their `granteeId` and finds all Groups they belong to through their Memberships. Then it looks at all Subscription Plans attached to those Groups and checks if any of those Plans include the requested Entitlement. After validating that the Subscriptions are active, it returns the Entitlements along with their expiry dates.
## Common Use Cases
### Individual User Subscriptions
For single-user Subscriptions, the simplest approach is to use the same ID for both Owner and Grantee:
```bash
# Create a Group with the user as both Owner and Grantee
POST /api/groups
{
"owner": "user_123",
"name": "User 123's Personal Workspace",
"grantees": [
{
"granteeId": "user_123",
"name": "John Doe"
}
]
}
```
### Team Subscriptions
For team Subscriptions, create a Group with a team identifier as the Owner:
```bash
# Create a Group for a team
POST /api/groups
{
"owner": "team_acme",
"name": "Acme Corp Development Team",
"grantees": [
{
"granteeId": "user_alice",
"name": "Alice Smith"
},
{
"granteeId": "user_bob",
"name": "Bob Johnson"
}
]
}
```
### Multi-Team Management
One Owner can manage multiple teams by creating separate Groups for each. This is perfect for larger organisations with different departments:
```bash
# Engineering team
POST /api/groups
{
"owner": "company_xyz",
"name": "Engineering",
"grantees": [...]
}
# Marketing team
POST /api/groups
{
"owner": "company_xyz",
"name": "Marketing",
"grantees": [...]
}
```
## Creating Groups
Groups can be created in two ways: explicitly through the dashboard or API, or automatically during Checkout if you haven't created one yet. Creating a Group beforehand lets you set up team members before purchase.
You can create an empty Group and add members later, or include Grantees from the start.
### API: Create a Group
**Endpoint:** `POST /api/groups`
**Request Body:**
```json
{
"name": "Development Team",
"owner": "company_acme",
"grantees": [
{
"granteeId": "user_alice",
"name": "Alice Smith"
},
{
"granteeId": "user_bob",
"name": "Bob Johnson"
}
]
}
```
The `grantees` array is optional. When you include Grantees in your request, Salable handles all the entity creation. It creates Owners and Grantees if they don't exist, and establishes Membership records to link Grantees to the Group. If Grantees already exist elsewhere, new Memberships are simply added without duplicating the Grantee records.
**Response:**
```json
{
"data": {
"id": "Group_01HXXX",
"organisation": "org_xxx",
"ownerId": "Owner_01HYYY",
"name": "Development Team",
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T10:00:00Z"
}
}
```
### Dashboard: Create a Group
In the dashboard, navigate to **Groups** in the sidebar. You'll see a "Create a Group" form where you can enter a Group name (optional) and an Owner ID (required). Click **Create Group** and you'll have an empty Group ready to go. From there, you can add Grantees individually through the Group management interface.
## Managing Grantees
### Adding Grantees to Groups
You can add Grantees to a Group at any time, whether before or after creating a Subscription.
**API Endpoint:** `POST /api/groups/{groupId}/grantees`
**Request Body:**
```json
[
{
"type": "add",
"granteeId": "user_charlie",
"name": "Charlie Brown"
}
]
```
**Dashboard:**
Navigate to **Groups** and click the **Manage Group** icon (eye icon) for the Group you want to modify. You'll see an "Add a grantee" form where you can enter a Grantee name (optional) and Grantee ID (required). Click **Add Grantee** and they'll be added to the Group through a new Membership record.
> **Note** If the Group has per-seat Plans attached, you cannot exceed the allocated seat count.
### Removing Grantees from Groups
Removing a Grantee from a Group is straightforward, but it's important to understand what happens: you're removing their Membership, not deleting the Grantee entirely. The Grantee still exists and can be added back to this Group or to other Groups later.
**API Endpoint:** `POST /api/groups/{groupId}/grantees`
**Request Body:**
```json
[
{
"type": "remove",
"granteeId": "user_charlie"
}
]
```
**Dashboard:**
Navigate to **Groups**, click **Manage Group**, find the Grantee in the table, and click the **Sign Out** icon. This removes their Membership from the Group while preserving the Grantee record itself.
### Replacing Grantees
Sometimes you need to swap one Grantee for another—like when a team member leaves and someone new joins. Instead of removing one and adding another in separate steps, you can replace them in a single atomic operation.
**API Endpoint:** `POST /api/groups/{groupId}/grantees`
**Request Body:**
```json
[
{
"type": "replace",
"granteeId": "user_old",
"newGranteeId": "user_new",
"name": "New User Name"
}
]
```
This maintains seat limits while rotating access. The old Grantee's Membership is removed and the new one is added simultaneously, keeping your seat count consistent throughout the transition.
### Batch Operations
When you need to make multiple changes at once, batch them in a single API request.
```json
[
{ "type": "remove", "granteeId": "user_1" },
{ "type": "remove", "granteeId": "user_2" },
{ "type": "add", "granteeId": "user_3", "name": "User Three" },
{ "type": "add", "granteeId": "user_4", "name": "User Four" }
]
```
Remove operations are processed first, then add operations. This ordering ensures seat limits are respected—you free up seats before trying to fill them.
## Seat Management
When a Group has per-seat Subscription Plans attached, seat quantity limits how many Grantees can be in that Group.
### How Seats Work
Per-seat **[Line Items](/docs/core-concepts#line-item)** define seat-based pricing, and each Subscription Plan has a quantity that represents the number of seats. When you try to add Grantees to a Group, Salable validates the Group size against the seat count. You cannot add more Grantees than you have seats for. If the Group has multiple Plans with per-seat pricing, the lowest seat count across all Plans becomes the limiting factor.
### Example: Seat Constraints
Imagine a Group called "Acme Dev Team" with 5 current members. The Group has Plan A with 10 seats and Plan B with 7 seats. Since the lowest count is 7, that's the maximum Group size—leaving 2 available seats.
### Viewing Seat Information
In the dashboard, navigate to **Groups**, click **Manage Group**, and look at the **Plans** section. The "Seats" column shows allocated seats for each Plan, and the seat count that's limiting your Group size is shown in bold.
### Increasing Seats
When you need to add more Grantees than your current seat allocation allows, you'll need to increase the seat count first. In the **Plans** section, find the Plan and click the seats icon in the **Actions** column. Enter a new value in the **Seats** field and click **Update Seats**. The seat count is updated via Stripe, and then you can add more Grantees.
**API:**
```bash
PUT /api/plan-items/{subscriptionPlanLineItemId}
{
"quantity": 15
}
```
### Pre-Purchase Team Setup
One powerful pattern is adding Grantees to Groups before purchasing a Subscription. Create a Group with Grantees, add the Group to a Cart, and when checking out, ensure the quantity matches or exceeds the Group size. After Checkout, all Grantees immediately have access. This approach lets you onboard teams upfront, collect user information before payment, and simplify the post-purchase experience.
## Subscriptions and Groups
### Linking Subscriptions to Groups
Subscriptions are linked to Groups through Subscription Plans. Each Subscription Plan within a Subscription is associated with a specific Group. When adding items to a Cart during Checkout, you can specify which Group should receive access:
```bash
POST /api/carts/{cartId}/items
{
"planId": "Plan_01HXXX",
"groupId": "Group_01HYYY"
}
```
If you don't provide a `groupId`, Salable creates a new empty Group automatically.
### Multiple Plans per Group
A single Group can have multiple Subscription Plans from different Subscriptions. For example, your "Engineering Team" Group might have Subscription A with a Pro Plan (10 seats) and Subscription B with an Analytics Add-on (5 seats). The same lowest-seat-count rule applies here—the Group would be limited to 5 members.
### Viewing Group Subscriptions
In the dashboard, navigate to **Groups**, click **Manage Group**, and look at the **Subscriptions** section. You can click the magnifying glass icon to filter Entitlements by Subscription or click the eye icon to view full Subscription details. Via the API, a simple `GET /api/groups/{groupId}` call returns all Subscription Plans linked to the Group.
## Checking Access
### API: Check Entitlements
To check if a Grantee has access to a feature:
**Endpoint:** `GET /api/entitlements/check`
**Query Parameters:**
- `granteeId` (required): The Grantee to check
- `owner` (optional): Filter to Subscriptions owned by this Owner
**Example:**
```bash
GET /api/entitlements/check?granteeId=user_alice
```
**Response:**
```json
{
"entitlements": [
{
"type": "entitlement",
"value": "advanced_features",
"expiryDate": "2024-02-15T10:00:00Z"
},
{
"type": "entitlement",
"value": "priority_support",
"expiryDate": "2024-02-15T10:00:00Z"
},
{
"type": "meter",
"value": "api_calls",
"expiryDate": "2024-02-15T10:00:00Z"
}
],
"signature": "hex_signature_string"
}
```
### Subscription Status Handling
Entitlements are returned for Subscriptions with these statuses:
- `active`: Subscription is active and paid
- `trialing`: Subscription is in trial period
- `past_due`: Only if the Product setting "Return Entitlements While Past Due" is enabled
### Testing Access in Dashboard
**Dashboard:**
1. Navigate to **Entitlements** → **Check**
2. Enter a **Grantee ID**
3. Optionally enter an **Owner ID** to filter results
4. Click **Check Entitlements**
5. View the results showing all accessible Entitlements
## Group Lifecycle Management
### Updating Groups
**API:** `PUT /api/groups/{groupId}`
**Request Body:**
```json
{
"name": "Updated Group Name",
"owner": "updated_owner_id"
}
```
**Dashboard:**
1. Navigate to **Groups** → **Manage Group**
2. Update the "Group Name" or "Owner" fields
3. Click **Save Group**
### Deleting Groups
**API:** `DELETE /api/groups/{groupId}`
**Dashboard:**
1. Navigate to **Groups**
2. Find the Group in the table
3. Click the **Trash** icon
4. Confirm deletion
> **Warning** Deleting a Group will remove all Memberships (Grantees lose access), potentially affect Subscriptions linked to the Group, and cannot be undone.
## Advanced Patterns
### Cross-Team Grantees
A single Grantee can belong to multiple Groups, even across different Owners:
```
Grantee "user_alice"
├─ Member of Group "Acme Engineering" (owner: acme_corp)
└─ Member of Group "Beta Industries Dev" (owner: beta_industries)
```
This enables scenarios like consultants working across multiple clients, shared team members between departments, or multi-organisation access for admins.
**Checking access with Owner filter:**
```bash
# Check Alice's access within Acme Corp
GET /api/entitlements/check?granteeId=user_alice&owner=acme_corp
# Check Alice's access within Beta Industries
GET /api/entitlements/check?granteeId=user_alice&owner=beta_industries
```
### Anonymous to Authenticated Conversion
For applications where users can start without authentication, you can use a temporary Owner ID during the purchase flow and convert it later. Start by creating a Cart with a temporary Owner ID like a session ID. The user completes Checkout without authentication, and a Group is created with that temporary Owner. After the user signs up, you update the Owner to their authenticated ID.
```bash
# After signup, update the Group Owner
PUT /api/groups/{groupId}
{
"owner": "user_authenticated_123"
}
```
### Handling Team Growth
As teams grow beyond seat limits, you'll want to handle this gracefully. Monitor Group size as it approaches seat limits, notify admins when capacity is reached, provide UI to increase seat counts, and automatically adjust Subscriptions via Stripe. Here's a simple implementation approach:
```javascript
// Check if group is at capacity
const maxSeats = getLowestSeatCount(group.plans);
const currentSize = group.members.length;
const isAtCapacity = currentSize >= maxSeats;
if (isAtCapacity) {
// Show upgrade prompt or prevent adding members
}
```
## Best Practices
### Seat Management
Ensure your application enforces that seat counts remain equal to or greater than Group size to avoid access issues. Provide tools for customers to monitor their seat utilisation so they can optimise costs and plan for growth. Consider alerting customers before they reach capacity so they can increase seats proactively rather than reactively.
### Context-Aware Access Control
When Grantees belong to multiple organisations, use the `owner` parameter to filter Entitlement checks to the relevant context. This prevents Entitlements from other organisations bleeding through.
### Security
Validate that `granteeId`s match authenticated users to prevent unauthorised access. Never expose internal database IDs (the `id` fields) to end users—use your own identifiers instead. Take advantage of the signature in Entitlement responses to verify authenticity and implement rate limiting on Entitlement checks to protect against abuse.
### Data Consistency
Create Groups before Checkout when possible to have better control over the process. Ensure Cart quantities match Group sizes for seated Plans to avoid validation errors. Handle edge cases where Grantees belong to multiple Groups, and regularly audit Group Memberships and Subscription status to catch any inconsistencies early.
## Troubleshooting
### Grantee Has No Access
When a Grantee should have access but doesn't, work through these checks systematically. First, verify the Grantee exists by calling `GET /api/grantees?groupId={groupId}`. Then check if the Grantee is in any Groups by looking at their Memberships. Do those Groups have active Subscription Plans? Are the Subscriptions in a valid state like active or trialing? Finally, confirm that the Plans include the Entitlement you're checking for.
### Cannot Add Grantee to Group
If you see "This group has reached its maximum number of grantees," you've hit the seat limit. Check the current Group size against seat limits, increase the seat count on per-seat Line Items, or remove existing Grantees before adding new ones.
### Seat Limits Not Matching Expectations
When your Group size limit is lower than you expect, check all Plans attached to the Group. The lowest seat count determines the limit, so increase seats on that Plan or remove it.
### Entitlements Not Updating
If you've changed a Subscription but the Grantee still has (or doesn't have) access, there are a few possible causes. The Subscription status might not have updated yet—wait for the Stripe webhook to process. The Product setting "Return Entitlements While Past Due" might be affecting results. You might be looking at the wrong Grantee or Owner. Or your application cache needs refreshing. To debug, check the raw Entitlement check response, verify Subscription status in the dashboard, confirm Grantee Group Memberships, and review webhook event logs.
## Summary
The Grantee and Group system provides flexible access management for subscription-based applications. Owners scope Subscription data, Groups organise Grantees under those Owners, and Grantees are the individual identities that receive access. Subscription Plans are linked to Groups rather than individual Grantees, and seats control the maximum Group size for per-seat Plans. When checking Entitlements, the system traverses from Grantee to Groups to Subscriptions to Plans.
---
### Subscriptions & Billing
Source: https://beta.salable.app/docs/subscriptions-and-billing
# Subscriptions & Billing
## Understanding Subscriptions
### How Subscriptions Are Created
Subscriptions are created when a customer completes checkout. Stripe notifies Salable of the successful payment, and Salable creates a Subscription from the Plans purchased in the [Cart](/docs/core-concepts#cart).
The Subscription inherits properties from the Cart—currency, billing interval, Grantee Group assignments, and any metadata—and belongs to the [Owner](/docs/core-concepts#owner) who made the purchase. If the Cart included Grantee Group assignments, these Groups are automatically linked to the appropriate [Subscription Items](/docs/core-concepts#subscription-item), giving team members immediate access to [Entitlements](/docs/core-concepts#entitlement).
### Subscription Status
Subscriptions move through several states during their lifecycle:
- **active**: in good standing with successful payments; full Entitlement access
- **past_due**: payment failed; Stripe is retrying via [Smart Retries](https://stripe.com/blog/how-we-built-it-smart-retries). You can configure whether customers retain Entitlement access during this period
- **cancelled**: terminated and will not renew. Immediate cancellation revokes access right away; end-of-period cancellation maintains access until the billing cycle ends
- **trialling**: trial period with full access but no charge yet. Transitions to active when the trial ends, unless cancelled
- **incomplete**: initial payment failed. Stripe retries briefly before moving to cancelled
### Subscription Items
Subscriptions contain [Subscription Items](/docs/core-concepts#subscription-item), one for each Plan purchased. A customer buying your main Product plus add-ons will have multiple Subscription Items tracked separately.
Within each Subscription Item, individual Line Items maintain their own quantities. A per-seat Line Item tracks purchased seats, a consultancy Line Item might track purchased hours, and a metered Line Item tracks usage records.
Subscription Items inherit Entitlements from their Plan and can be associated with Grantee Groups. If a customer assigned a team Plan to a Grantee Group during checkout, that association persists, ensuring all group members receive the appropriate Entitlements.
## Managing Subscription Items
You can add, remove, or replace Plans on a Subscription in a single request. When making changes, you specify the proration behaviour to control how billing is handled. All actions in the request are processed together to calculate a single billing adjustment. Plans must match the Subscription's currency and billing interval.
- **Add** adds a new Plan to the Subscription.
- **Remove** removes an existing Plan from the Subscription. You cannot remove the last one.
- **Replace** swaps one Plan for another. Useful for upgrades or downgrades.
### Tier Tags
If your Plans use [Tier Tags](/docs/core-concepts#tier-tags-and-tier-sets), you can replace one Plan with another from the same tier set. For example, if a customer is on the "Basic" Plan, it can be replaced with the "Pro" Plan even if they share the same tier tag. This is how to handle upgrades/downgrades within a tier set.
You cannot add a Plan that shares a tier tag with a Plan already on the Subscription. If you need to move a customer to a different Plan within the same tier set, use the replace action rather than adding the new Plan separately.
## Quantity Management
After a customer purchases a Subscription, you can adjust the quantity of each Line Item on their Plan. When making a change, you specify the proration behaviour to control how billing is handled. The minimum and maximum limits configured on the Line Item are enforced.
For per-seat Line Items, the quantity determines how many seats are available for the associated Grantee Group. You can increase the quantity at any time to add seats, but you cannot decrease it below the current number of group members. You must remove members from the group first.
## Proration
Proration ensures fair billing when Subscriptions change mid-cycle. When a customer upgrades, downgrades, adds Plans, or changes quantities between billing dates, the amount owed for the partial period is prorated based on the time remaining until the next billing anchor.
For example, if a customer with a \$100/month Plan upgrades to a \$150/month Plan on day 10 of a 30-day cycle, the prorated charge is approximately \$33.33 (the \$50 difference × 20/30 remaining days). At the next billing date, the full \$150 is charged. This applies equally to seat additions, plan changes, and add-ons—all changes synchronise to the single billing anchor, so customers receive one invoice per period.
### Proration Behaviours
Salable provides the following proration behaviours:
- **create_prorations** immediately calculates the difference between the old and new Subscription value and generates an invoice or credit right away. If a customer upgrades from a \$50/month Plan to a \$100/month Plan halfway through the month, they're immediately charged approximately \$25 (the prorated difference for the remaining half-month). At their next regular billing date, they'll be charged the full \$100 for the upcoming month.
- **always_invoice** creates an invoice immediately for any proration amount, even if the change results in a credit. This ensures you're always generating invoices for audit purposes, though credits still apply to the customer's balance for future billing.
- **none** turns off proration entirely. If you upgrade a Plan with this setting, the customer continues paying the old rate until their next billing date, at which point they start paying the new rate. This is simpler for customers to understand and avoids mid-cycle charges, but it means you may be giving away upgraded access for free during the transition period. This works well for downgrades when you want to be generous and let customers keep premium features until the end of their paid period.
### Choosing the Right Proration Strategy
The proration behaviour you specify depends on the nature of the update. Here are a few strategies:
- Upgrades: **create_prorations** is typically the right choice. Customers understand they're paying more for better features, and immediate billing ensures you're compensated for the value you're providing.
- Downgrades: **none** can be generous—customers keep premium features until the end of their paid period before switching to the lower price.
- Add-ons: **create_prorations** is a sensible option because they're actively asking for a feature and expect to pay for it. The immediate charge confirms they have access right away.
- Complex modifications (_eg_ multiple updates): **create_prorations** calculates the net difference, potentially resulting in a small charge, a small credit, or nearly zero change.
## Cancellation Management
### End-of-Period Cancellation
Schedule a Subscription to cancel at the end of the current billing period by disabling auto-renewal. The Subscription remains active until the end of the period, then terminates without renewal. No refund is issued—the customer receives the full value of what they paid.
After scheduling, the Subscription includes `cancelAtPeriodEnd: true`. Use this to show customers when access ends. To reverse a scheduled cancellation, re-enable auto-renew.
### Immediate Cancellation
Immediate cancellation terminates the Subscription and revokes Entitlements. A prorated refund is issued for the unused portion of the billing period.
Use immediate cancellation for terms of service violations, account deletions, or when switching billing intervals.
## Billing Cycles and Intervals
### How Billing Anchors Work
Every Subscription has a billing anchor date—the day of the month when the Subscription renews and the next billing period begins. This anchor is set when the Subscription is first created, based on the date the customer completed checkout.
If a customer subscribes on January 15th with monthly billing, their anchor is the 15th. They'll be billed on the 15th of each month for the upcoming period.
This anchor-based billing provides customers with consistency—they know precisely when charges will occur each month. For annual Subscriptions, the anchor works the same way, spanning years instead of months.
### Month-End Edge Cases
Billing anchors on the 29th, 30th, or 31st of the month present special challenges due to varying month lengths.
Stripe and Salable handle this by billing on the last available day of shorter months. A January 31st anchor bills on February 28th (or 29th in leap years), then March 31st, April 30th, May 31st, and so on. The anchor remains conceptually tied to the 31st, but actual billing happens on the last available day of each month.
This behaviour ensures customers never miss a billing date while keeping the cycle as close to monthly as possible. However, it does mean billing periods vary slightly in length—a billing period ending on February 28th is shorter than one ending on March 31st.
## Invoice Management
Stripe generates an invoice for every Subscription billing event. Each invoice includes Line Items showing what was billed, payment status, total amount, and an `invoicePdf` URL for downloadable PDF receipts.
For metered Line Items, invoices show usage charges with quantity consumed, per-unit price, and total (_eg_ "API Calls: 1,450 × \$0.10 = \$145.00").
You can also preview what customers will be charged on their next billing date before it's finalised. The preview includes recurring charges, pending metered usage, account credits, and the final amount.
## Price Synchronisation
### Understanding Price Changes
When you update a price in Salable, existing Subscriptions continue billing at the old price by default—existing customers are "grandfathered" into price updates.
> **Note** You can configure the billing model to apply updated prices to all customers.
### Syncing to New Prices
Use the Price Synchronisation endpoint to migrate a Subscription to the latest pricing. This updates the Subscription Items to reference the current prices for their Plans, applying your new pricing to existing customers.
The proration behaviour determines when the new pricing takes effect. Using `none` means the Subscription continues at the old price through the current billing period, then switches to the new price at the next renewal. Using `create_prorations` means the price change takes effect immediately with appropriate prorated charges or credits.
### Communicating Price Changes
Best practices for price changes:
- Announce updates at least 30 days in advance
- Explain the reasons for the price update and any value changes
- Clearly state the dates when the change takes effect for existing customers
- Consider offering options to lock in current pricing
Using end-of-period price synchronisation respects the customer's current billing cycle while giving them time to adjust.
> **Important** Price notification requirements vary by jurisdiction—some require 30 days' notice, others 60 days or more. Research legal requirements for your customer locations or consult legal counsel before implementing price changes.
## Payment Failures and Dunning
### Understanding Past Due Status
If a scheduled Subscription payment fails, the Subscription enters a `past_due` state. Stripe automatically attempts [Smart Retries](https://docs.stripe.com/billing/revenue-recovery/smart-retries), adapting the retry schedule based on failure reason and historical patterns.
During this period, you can control whether customers retain Entitlement access. Maintaining access can reduce frustration if the failure is temporary, but it means providing service without current payment. The right choice depends on your risk tolerance.
### 3D Secure and Strong Customer Authentication
In the European Economic Area, Strong Customer Authentication (SCA) regulations require additional verification for certain transactions. This is commonly implemented through 3D Secure (3DS). While recurring Subscription payments benefit from exemptions, several scenarios trigger authentication:
- **Price increases** beyond certain thresholds (often 30 EUR or equivalent)
- **Initial Subscription payments** (handled automatically by Stripe checkout)
- **Payment method changes**
- **Large mid-cycle charges** from upgrades or expensive add-ons
When a payment fails due to SCA, the customer must actively authenticate. Updating their card won't resolve the issue. Notify customers that verification is required for security purposes, and provide Stripe's billing portal to complete the authentication challenge.
For price increases or mid-cycle modifications that may trigger authentication, communicate proactively. Mention that their bank may require verification, and consider offering the option to delay changes until the next billing date to avoid mid-cycle triggers.
> **Note** SCA requirements are mandatory in the European Economic Area. Other regions may have different requirements or none at all. Consider your customer distribution when planning communication strategies.
### Customer Communication During Dunning
While Stripe handles payment retries, you are responsible for customer communication. If a payment fails, proactively notify the customer through email or in-app messages. Explain the failed payment, provide a link to update their payment method, and reassure them about how much time they have before access is affected.
If retries continue to fail, escalate urgency. After several days, make it clear that cancellation is imminent.
If a payment succeeds after a retry, confirm the charge was successful so customers know the issue is resolved.
### Providing Payment Update Links
The easiest way for customers to update their payment method is through Stripe's hosted billing portal. You can generate portal links that take customers directly to a secure page where they can update cards, view billing history, and manage their Subscription. Include these portal links prominently in your communications about payment failures.
---
### Cart & Checkout
Source: https://beta.salable.app/docs/cart-and-checkout
# Cart & Checkout
## Overview
Salables' Cart system provides a flexible way to build shopping experiences for your Subscription Products. Whether you're implementing a self-service pricing page or building a custom checkout flow, Carts let customers select multiple Plans and complete the purchase in a single transaction.
Carts support anonymous users who may not have signed up for your application, making it possible for guests to add Plans to their Cart before creating an account. When they do sign up, you simply update the Cart's Owner to link to their new account.
## How Carts Work
A Cart belongs to an [**Owner**](/docs/core-concepts#owner) (an identifier used to scope the Subscription) and contains Cart items representing the Plans the customer wants to purchase. Each Cart has a billing **interval** (_ege_ monthly, yearly etc), and can optionally specify a **currency**. If no currency is provided, Stripe determines one during checkout based on the customer's location.
When you add items to a Cart, Salable validates that the selected Plans support the Cart's currency and interval. This ensures customers only see compatible pricing options at checkout. The Cart can have three states: **active** for Carts currently being built, **complete** for Carts that have successfully checked out, and **abandoned** for Carts that were explicitly marked as no longer needed.
When the customer is ready to purchase, you simply generate a Stripe Checkout session URL from the Cart. After successful payment, a Subscription is created with the specified Grantee Groups and Entitlements.
## Cart Concepts
### Owner
The Owner is the entity responsible for paying for the Subscription. For individual Subscriptions, this might be a user ID. For team or organization Subscriptions, it's typically an organization or company ID. The Owner identifier is flexible—you choose what makes sense for your business model.
Owners are created automatically when you create a Cart. If an Owner with that identifier already exists in your organization, Salable reuses it. This means you can create multiple Carts for the same Owner without duplicating Owner records.
### Currency and Interval
**Providing an Explicit Currency (Recommended)**
It is recommended to explicitly define the currencies you would like your Product to support to provide an easier checkout experience. When you provide a currency:
- You can design different pricing strategies for different markets—offering region-specific discounts or adjusting prices based on purchasing power parity
- You can target specific currencies without needing to configure all currency options across every Line Item
- Salable cherry-picks only the Line Items that have pricing in that specific currency, ensuring customers see consistent prices from your pricing page through to Stripe checkout
- You prevent confusion from currency mismatches—what customers see on your site matches what they pay at checkout
This approach provides the most control and the most predictable customer experience.
**Omitting Currency (Geolocation Mode)**
If you opt not to specify a currency, Stripe will use geolocation at checkout to automatically determine the customer's currency. This approach has some trade-offs:
- Stripe detects the customer's location and displays prices in their local currency
- Salable includes all Line Items from the Plans in your Cart (no cherry-picking)
- The currency shown at checkout might differ from what you displayed on your pricing page if the customer is in a different region
- All Line Items across all Plans must have the same default currency (a Stripe requirement)
- You must configure pricing for all currencies you want to support across all Line Items
**Critical Requirement for Geolocation Mode**
If you omit currency, all Line Items across all Plans in your Cart must share the same default currency. This is a Stripe requirement. For example, if Plan A's Line Items default to USD and Plan B's Line Items default to GBP, the checkout will fail. In this case, you must provide an explicit currency when creating the Cart.
You don't need to specify an interval and interval count when creating a Cart, but you must provide one when adding the first item. The interval can be **day**, **week**, **month**, or **year** for plans with recurring line items. Or, for one-off line items it can be **null**. Once the first item sets the interval and interval count, all subsequent items added to the Cart must use the same interval (or currency for one off items).
### Cart Items
A Cart item represents a Plan that the customer wants to purchase. Each Cart item includes the Plan ID, optional metadata for specifying quantities above the minimum for specific Line Items, and optionally a Grantee ID or Grantee Group ID to assign access.
The metadata structure is an object where keys are Line Item slugs and values are objects with quantity information. You only need to include Line Items in metadata when setting their quantity above the configured minimum. Line items omitted from metadata automatically use their minimum quantity. Metered Line Items should never be included in metadata.
### Cart Status
Carts move through different statuses during their lifecycle. **Active** Carts are being built—you can add items, remove items, and modify them. **Complete** Carts have been successfully checked out and converted into Subscriptions. **Abandoned** Carts have been explicitly marked as no longer needed, which helps you track Cart abandonment rates.
### One-Off Purchases
Salable supports one-off purchases through the Cart system. They can be purchased individually or alongside recurring plans that share the same currency.
After purchasing a one-off item, a Receipt is generated as a proof of purchase which can be viewed in the Salable dashboard. A `receipt.created` webhook event is also emitted.
**Creating One-Off Only Carts**
For purchases that contain only one-off items (no recurring charges), create the Cart with `interval` and `intervalCount` set to `null`:
```json
{
"owner": "company_acme",
"currency": "USD",
"interval": null,
"intervalCount": null
}
```
When adding items to a one-off Cart, also set `interval` and `intervalCount` to `null`:
```json
{
"cartId": "Cart_01HXXX",
"planId": "Plan_01HYYY",
"interval": null,
"intervalCount": null
}
```
**Mixed Plans: One-Off Plus Recurring**
Plans can contain both one-off and recurring Line Items. When you add such a Plan to a Cart with a specified interval and currency:
- Recurring Line Items that match the Cart's interval and have pricing in the Cart's currency are included
- One-off Line Items that have pricing in the Cart's currency are automatically included (they don't need to match the interval)
For example, consider a Plan with three Line Items: a $99 one-time setup fee, a $29/month base subscription, and a $10/user/month per-seat charge. When you add this Plan to a monthly USD Cart, all three Line Items are included. The customer sees a checkout with the one-time setup fee plus the recurring monthly charges. The setup fee appears only on the first invoice, while the subscription and per-seat charges recur each month.
## Creating a Cart
### API: Create Cart
**Endpoint:** `POST /api/carts`
**Request Body:**
```json
{
"owner": "company_acme",
"currency": "USD",
"interval": "month",
"intervalCount": 1
}
```
Or for geolocation-based currency selection:
```json
{
"owner": "company_acme",
"interval": "month"
}
```
**Parameters:**
The **Owner** is a string identifier used to scope the Subscription and its related data. The **currency** is optional—provide a three-letter currency code in uppercase (automatically converted if you provide lowercase) for explicit currency selection, or omit it entirely to let Stripe use geolocation to determine the best currency for the customer. The **interval** is optional and can be set to `day`, `week`, `month`, or `year`, or omitted to set it later. The interval count can also be specified optionally with the **intervalCount** parameter which accepts a number.
**Response:**
```json
{
"data": {
"id": "Cart_01HXXX",
"organisation": "org_xxx",
"ownerId": "Owner_01HYYY",
"currency": "USD",
"interval": "month",
"intervalCount": 1,
"status": "active",
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T10:00:00Z"
}
}
```
### Creating Carts for Anonymous Users
For anonymous users who haven't authenticated yet, use a temporary session identifier as the Owner. This could be a session ID from your application, a temporary UUID, or any unique identifier you can track.
```javascript
async function createAnonymousCart(sessionId, currency, interval) {
const body = {
owner: `session_${sessionId}`,
interval
};
// Only include currency if provided
if (currency) {
body.currency = currency.toUpperCase();
}
const response = await fetch('https://api.salable.app/api/carts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SALABLE_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
if (!response.ok) {
throw new Error(`Failed to create Cart: ${response.status}`);
}
const { data: cart } = await response.json();
return cart;
}
// Example: Create a Cart when user visits pricing page
app.post('/api/create-cart', async (req, res) => {
const { sessionId, currency, interval } = req.body;
try {
const cart = await createAnonymousCart(sessionId, currency, interval);
res.json({ cartId: cart.id });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
```
### Creating Carts for Authenticated Users
For authenticated users, use their user ID or organisation ID as the Owner identifier directly.
```javascript
async function createAuthenticatedCart(userId, currency, interval) {
const body = {
owner: userId,
interval
};
// Only include currency if provided
if (currency) {
body.currency = currency.toUpperCase();
}
const response = await fetch('https://api.salable.app/api/carts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SALABLE_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
if (!response.ok) {
throw new Error(`Failed to create Cart: ${response.status}`);
}
const { data: cart } = await response.json();
return cart;
}
```
## Adding Items to a Cart
You can add multiple different Plans to a Cart, but each Plan can only be added once. If you need multiple quantities, adjust the quantity in the Line Item metadata rather than adding the same Plan multiple times.
### API: Create Cart Item
**Endpoint:** `POST /api/cart-items`
**Request Body:**
```json
{
"cartId": "Cart_01HXXX",
"planId": "Plan_01HYYY",
"interval": "month",
"intervalCount": 1,
"metadata": {
"per_seat_charge": { "quantity": 5 }
},
"grantee": "user_alice"
}
```
**Parameters:**
The **cartId** identifies which Cart to add the item to. The **planId** specifies which Plan the customer wants to purchase. The **interval** sets or confirms the Cart's billing interval—use `day`, `week`, `month`, or `year` for recurring purchases. For adding only one-off line items, set **interval** to `null`. If the Cart already has an interval set, the value must match. The **intervalCount** works alongside interval to define the billing frequency (_eg_ `2` with `week` for biweekly billing). For one-off purchases, set **intervalCount** to `null`. The **metadata** object is optional and maps Line Item slugs to quantity objects—you only need to include Line Items where the quantity should be above the configured minimum. Line items not included in metadata will use their minimum quantity. Never include metered Line Items in metadata. The **grantee** is optional and can be a grantee ID or a group ID (starting with `grp_`).
**Response:**
```json
{
"data": {
"id": "CartItem_01HZZZ",
"organisation": "org_xxx",
"cartId": "Cart_01HXXX",
"planId": "Plan_01HYYY",
"metadata": { ... },
"granteeId": "user_alice",
"groupId": null,
"createdAt": "2024-01-15T10:05:00Z",
"updatedAt": "2024-01-15T10:05:00Z"
}
}
```
### Understanding Metadata
The metadata structure specifies quantities for Line Items when you need to set them above their minimum values. Each Line Item slug maps to an object with a `quantity` property. You only need to include Line Items in the metadata when their quantity should be higher than the configured minimum—if you omit a Line Item from the metadata, Salable automatically uses the minimum quantity for that Line Item.
```javascript
// Example: Plan with three Line Items
const metadata = {
// Only include Line Items where quantity > minimum
user_seats: { quantity: 10 } // Per-seat Line Item, setting 10 seats
// monthly_base would use its minimum (typically 1) if not specified
// api_calls is metered, so it's not included here
};
```
**Important:** Do not include metered Line Items in the metadata. Metered Line Items have their quantities tracked through usage recording after the Subscription is created. Including a metered Line Item in your Cart metadata will result in an error. The quantity for metered items can only be incremented after purchase when usage is recorded.
### Adding Items Example
```javascript
async function addItemToCart(cartId, planId, interval, intervalCount, metadata, granteeId) {
const response = await fetch('https://api.salable.app/api/cart-items', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SALABLE_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
cartId,
planId,
interval,
intervalCount,
metadata,
...(granteeId && { grantee: granteeId })
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.title);
}
const { data: cartItem } = await response.json();
return cartItem;
}
// Example: Add a Plan to Cart
app.post('/api/cart/add-plan', async (req, res) => {
const { cartId, planId, seats } = req.body;
try {
// Build metadata - only include Line Items with quantities above minimum
const metadata = {};
// Only add per-seat if quantity is above its minimum
if (seats > 1) {
// Assuming minimum is 1
metadata.per_seat = { quantity: seats };
}
// base_subscription uses its minimum (typically 1) when not specified
// metered Line Items are never included in metadata
const cartItem = await addItemToCart(cartId, planId, 'month', metadata, req.user.id);
res.json({ success: true, cartItem });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
```
### Validation Rules
The following validation rules are enforced when adding items to a Cart:
- The same Plan cannot be added more than once
- If the Cart has an explicit currency set, the Plan must have pricing configured for that currency and the Cart's interval.
- If the Cart's currency was omitted, all Line Items from the Plan are included.
- Quantities must be within the Line Item's minimum and maximum quantity limits. For per-seat Line Items, if you provide a Grantee Group ID, the quantity must be at least equal to the number of members in that Grantee Group.
- You cannot add a metered Line Item if the Owner already has an active Subscription with that same meter slug (to prevent duplicate usage tracking).
If [Tier Tags](/docs/core-concepts#tier-tags-and-tier-sets) are assigned to Plans, additional validation rules apply when adding items to a Cart:
- You cannot add multiple Plans with the same tier tag to the same Cart.
- You cannot add a Plan to a Cart if the Owner already has an active Subscription to another Plan with the same tier tag.
## Managing Cart Items
### Retrieving a Cart
**Endpoint:** `GET /api/carts/{cartId}`
Retrieve the full Cart with all its items, expanded metadata showing Line Item details, and quantity validation rules for each Line Item.
```javascript
async function getCart(cartId) {
const response = await fetch(`https://api.salable.app/api/carts/${cartId}`, {
headers: {
Authorization: `Bearer ${process.env.SALABLE_SECRET_KEY}`
}
});
if (!response.ok) {
throw new Error(`Failed to get Cart: ${response.status}`);
}
const { data: cart } = await response.json();
return cart;
}
// Display Cart contents to user
app.get('/api/cart/:cartId', async (req, res) => {
try {
const cart = await getCart(req.params.cartId);
// Transform for frontend display
const cartSummary = {
currency: cart.currency,
interval: cart.interval,
cartItems: cart.cartItems.map(item => ({
planName: item.plan.name,
lineItems: Object.entries(item.metadata).map(([slug, details]) => ({
name: details.name,
quantity: details.quantity,
priceType: details.priceType
}))
}))
};
res.json(cartSummary);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
```
### Removing Cart Items
**Endpoint:** `DELETE /api/cart-items/{cartItemId}`
Remove a specific item from the Cart. The Cart must be in **active** status to remove items.
```javascript
async function removeCartItem(CartItemId) {
const response = await fetch(`https://api.salable.app/api/cart-items/${cartItemId}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.SALABLE_SECRET_KEY}`
}
});
if (!response.ok) {
throw new Error(`Failed to remove Cart item: ${response.status}`);
}
// 204 No Content response
return true;
}
```
### Updating Cart Owner
**Endpoint:** `PATCH /api/carts/{cartId}`
Update the Cart's Owner, which is essential for converting anonymous Carts to authenticated user Carts.
**Request Body:**
```json
{
"owner": "user_alice_authenticated"
}
```
```javascript
async function updateCartOwner(cartId, newOwnerId) {
const response = await fetch(`https://api.salable.app/api/carts/${cartId}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.SALABLE_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
owner: newOwnerId
})
});
if (!response.ok) {
throw new Error(`Failed to update Cart: ${response.status}`);
}
const { data: cart } = await response.json();
return cart;
}
```
### Abandoning a Cart
**Endpoint:** `POST /api/carts/{cartId}/abandon`
Mark a Cart as abandoned. This is useful for tracking Cart abandonment metrics or cleaning up Carts that users explicitly closed without purchasing.
```javascript
async function abandonCart(cartId) {
const response = await fetch(`https://api.salable.app/api/carts/${cartId}/abandon`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SALABLE_SECRET_KEY}`
}
});
if (!response.ok) {
throw new Error(`Failed to abandon Cart: ${response.status}`);
}
// 204 No Content response
return true;
}
```
## Anonymous to Authenticated Flow
One of the most powerful features of Salable's Cart system is support for anonymous users. This pattern lets visitors explore your pricing, add Plans to their Cart, and then sign up only when they're ready to purchase.
### Step 1: Create Anonymous Cart
When an anonymous user visits your pricing page, create a Cart using a session identifier.
```javascript
// Frontend: When user visits pricing
fetch('/api/create-anonymous-cart', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionId: getSessionId(), // Your session tracking
currency: 'USD',
interval: 'month'
})
})
.then(res => res.json())
.then(({ cartId }) => {
// Store cartId in localStorage or state
localStorage.setItem('cartId', cartId);
});
```
### Step 2: Add Plans as Anonymous User
Let the anonymous user add Plans to their Cart normally. The Cart exists and functions fully before authentication.
```javascript
// Frontend: User clicks "Add to Cart" on pricing page
function addPlanToCart(planId, seats) {
const cartId = localStorage.getItem('cartId');
return fetch('/api/cart/add-plan', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
cartId,
planId,
seats
})
});
}
```
### Step 3: Prompt for Authentication
When the user proceeds to checkout, redirect them to sign up or log in if they haven't already.
```javascript
// Frontend: User clicks "Proceed to Checkout"
function proceedToCheckout() {
if (!isAuthenticated()) {
// Store intent to return to checkout after auth
localStorage.setItem('checkoutAfterAuth', 'true');
window.location.href = '/signup';
} else {
continueToCheckout();
}
}
```
### Step 4: Update Cart Owner After Authentication
Once the user completes authentication, update the Cart's Owner from the session ID to the authenticated user ID.
```javascript
// Backend: After successful signup/login
app.post('/api/auth/login', async (req, res) => {
// ... authentication logic ...
const user = authenticatedUser;
const cartId = req.body.cartId; // Passed from frontend
if (cartId) {
try {
await updateCartOwner(cartId, user.id);
} catch (error) {
console.error('Failed to transfer Cart:', error);
// Cart transfer failing shouldn't block login
}
}
res.json({ user, success: true });
});
```
### Step 5: Continue to Checkout
With the Cart now linked to the authenticated user, proceed to generate the checkout URL.
```javascript
// Frontend: After login redirect
if (localStorage.getItem('checkoutAfterAuth') === 'true') {
localStorage.removeItem('checkoutAfterAuth');
continueToCheckout();
}
```
## Generating Checkout URLs
### API: Generate Checkout Link
**Endpoint:** `POST /api/carts/{cartId}/checkout`
Generate a Stripe Checkout session URL for customers to complete payment.
**Request Body:**
```json
{
"successUrl": "https://yourapp.com/welcome",
"cancelUrl": "https://yourapp.com/pricing",
"email": "customer@example.com",
"allowPromoCodes": true,
"automaticTax": false,
"collectBillingAddress": true,
"collectShippingAddress": false,
"cardPrefillPreference": "choice",
"trialPeriodDays": 14
}
```
**Parameters:**
These parameters can be provided in the API call or configured in your Product settings. If a value exists in the Product settings, it will be used as the default. Providing a value in the API call overrides the Product settings.
**Required (unless configured in Product settings):**
- **successUrl** - URL where Stripe redirects customers after successful payment
- **cancelUrl** - URL where customers return if they abandon checkout
If multiple Products in your Cart have conflicting URL defaults, you must provide explicit values in the API call.
**Optional:**
- **email** - Pre-fills the customer email in the checkout form
- **allowPromoCodes** - Enables promotional code entry at checkout (boolean)
- **automaticTax** - Enables Stripe Tax for automatic tax calculation (boolean)
- **collectBillingAddress** - Requires billing address at checkout (boolean)
- **collectShippingAddress** - Requires shipping address at checkout (boolean)
- **cardPrefillPreference** - Controls saving payment methods: `none`, `choice`, or `always`
- **trialPeriodDays** - Number of days for trial period before billing begins (1-730 days)
**Response:**
```json
{
"data": {
"url": "https://checkout.stripe.com/c/pay/cs_live_..."
}
}
```
### Checkout Implementation
```javascript
async function generateCheckoutUrl(cartId, email, successUrl, cancelUrl) {
const response = await fetch(`https://api.salable.app/api/carts/${cartId}/checkout`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SALABLE_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
successUrl,
cancelUrl,
allowPromoCodes: true,
collectBillingAddress: true,
cardPrefillPreference: 'choice'
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.title);
}
const { data } = await response.json();
return data.url;
}
// Example: Handle checkout button
app.post('/api/cart/:cartId/checkout', async (req, res) => {
try {
const checkoutUrl = await generateCheckoutUrl(
req.params.cartId,
req.user.email,
`${process.env.APP_URL}/welcome`,
`${process.env.APP_URL}/pricing`
);
res.json({ checkoutUrl });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
```
### Checkout Behavior
When you generate a checkout URL, Salable performs several operations behind the scenes. It creates or updates Grantee Groups based on the Cart items. For Cart items without a Grantee Group ID, a new Grantee Group is created. If a Grantee ID was provided, it adds that Grantee to the new Grantee Group. For per-seat Line Items without a Grantee, it creates an empty Grantee Group that can be populated later.
The Cart status changes to **complete** after successful payment, preventing any further modifications. Once Stripe confirms the payment is successful Salable creates a Subscription, Subscription Plan records, and usage tracking for metered items in your organization.
## Best Practices
### Session Management for Anonymous Carts
Use a consistent session identifier throughout the anonymous user's journey. Store the Cart ID in localStorage or a cookie so it persists across page refreshes. When the user authenticates, make sure to transfer the Cart Ownership immediately to prevent losing their selections.
### Currency Selection Strategy
Choose the currency approach that best fits your business model and customer experience.
**Explicitly defining currency** is recommended when you know the customer's currency context. Detect the customer's location or preferences in your application, display your pricing page in that currency, and then create the Cart with that currency explicitly set. This approach provides several benefits: it ensures customers see consistent pricing from your pricing page through to Stripe checkout, preventing confusion from unexpected currency changes, and it gives you greater flexibility when designing your pricing models. With an explicit currency, you can implement region-specific pricing strategies—like offering discounts in emerging markets, adjusting for purchasing power parity, or experimenting with different price points in different currencies—without needing to configure every currency option across every Line Item.
You can use various methods to determine currency: detect the customer's location using their IP address with a geolocation service, allow customers to select their preferred currency from your pricing page, use the customer's browser locale or account settings, or default to your primary market currency. Once you know the currency, provide it explicitly when creating the Cart.
**Omitting currency** is a valid option when you want Stripe to handle currency detection automatically at checkout. This works well if you're displaying prices generically (without showing specific currency symbols or amounts) or if you're comfortable with Stripe determining the best currency for each customer. However, be aware that the currency shown at checkout may differ from what the customer saw on your pricing page if you displayed prices in a specific currency. This can lead to customer confusion or surprise when they encounter different pricing than expected.
**Key consideration**: When omitting currency, all Line Items across all Plans in your Product must have the same default currency configured. You can have prices in multiple currencies (USD, GBP, EUR, etc.), but one must be marked as default, and that default must be consistent across all Line Items. If you have Products with different default currencies, you must provide an explicit currency when creating Carts that include Plans from multiple Products.
### Validation Before Checkout
Before generating a checkout URL, validate that the Cart has at least one item and that all quantities are within acceptable ranges. This prevents errors during the Stripe checkout session creation.
### Multiple Plans in One Purchase
Let customers add multiple Plans to their Cart in a single purchase. This is convenient for base Subscriptions plus add-ons, or for purchasing access to multiple Products simultaneously. Each Plan can have different Line Items and pricing structures, and Salable handles the complexity of creating a single checkout.
> **Important**: Each Plan can only be added to the Cart once. Attempting to add the same Plan multiple times will fail. If you need multiple quantities of an item, use the quantity parameter on the Line Item instead.
### Handling Checkout Failures
Not all checkout sessions result in completed payments. Customers might abandon the Stripe checkout page, their payment might fail, or they might close the browser. Keep Carts in **active** status until you receive webhook confirmation of successful payment. This lets customers return to their Cart and try checking out again.
## Troubleshooting
### Cart Creation Fails with 400 Error
If Cart creation fails with a 400 error, check that the currency code is valid and recognized by Stripe. Common valid codes include USD, GBP, EUR, CAD, AUD, and many others. The currency must be supported by your Stripe account's configuration.
### Cannot Add Item: Plan Already in Cart
Each Plan can only be added to a Cart once. If you need different configurations of the same Plan (like different seat counts), you'll need to use different Plans rather than adding the same Plan multiple times. Alternatively, update the Cart item's metadata with the new quantities rather than adding a duplicate.
### Cannot Add Item: Invalid Metadata for Metered Line Item
If you include a metered Line Item in your Cart item metadata, the request will fail with an error. Metered Line Items track usage after Subscription creation and should never be included in the metadata object. Only include non-metered Line Items where you need to set quantities above their configured minimum.
### Cannot Add Item: Owner Already Subscribed to Metered Item
If you try to add a metered Line Item to a Cart and the Owner already has an active Subscription with that same meter slug, the request will fail. This prevents duplicate usage tracking which would cause billing issues. To resolve this, the customer would need to either cancel their existing Subscription with that meter or choose a different Plan.
### Checkout Link Generation Fails: Missing URLs
If you don't provide `successUrl` and `cancelUrl` in the checkout request and the Plans in the Cart don't have Product-level defaults configured, the request will fail. Always either configure these URLs in your Product settings or provide them explicitly in the checkout API call.
### Checkout Link Generation Fails: No Payment Integration or Missing Business Information
If checkout link generation fails with an error along the lines of "In order to use Checkout, you must set an account or business name," this indicates that your Stripe account is missing required information.
**For Test Mode:**
You must complete both the **business type** form and the **personal details** form in Stripe's onboarding. Navigate to Payment Integrations in your dashboard, access your Stripe Connect settings, and complete both required forms. You don't need to complete full onboarding (banking, identity verification) for test mode checkout links.
**For Live Mode:**
You must have a fully onboarded Stripe account with **Active** status. This includes business information, banking details, and identity verification. Navigate to Payment Integrations to check your onboarding status and complete any pending requirements.
### Checkout Link Generation Fails: Currency Default Mismatch
If you created a Cart without specifying a currency and checkout fails with a Stripe error about currencies, this means the Line Items in your Cart have different default currencies. For example, one Plan's Line Items might default to USD while another Plan's Line Items default to GBP.
To resolve this, either configure all your Line Items to use the same default currency, or create the Cart with an explicit currency that all Line Items support. When using explicit currency, Salable will cherry-pick only the Line Items with that currency, avoiding the conflict.
### One-Off Items Not Appearing in Cart
If your one-off Line Items aren't being included when you add a Plan to the Cart, check the currency configuration. One-off Line Items are automatically included regardless of the Cart's interval, but they still need to have pricing configured in the Cart's currency. If the Cart has an explicit currency set, ensure your one-off Line Items have a Price in that currency. And, if using cart geolocation for the currency, ensure the default currency of any one-off line items match the default currency of all other items in the cart.
## Summary
Salable's Cart system provides a flexible foundation for building checkout experiences. Create Carts for both anonymous and authenticated users, add multiple Plans with custom quantities, and generate Stripe Checkout sessions with a single API call. The system handles Owner management automatically, validates quantities and compatibility, and creates all necessary grantee groups during the checkout process.
The Cart system supports both recurring subscriptions and one-off purchases. For recurring billing, specify an interval like `month` or `year`. For one-off purchases, set `interval` and `intervalCount` to `null`. Plans can combine both one-off and recurring Line Items, Salable automatically includes one-off items regardless of the Cart's interval.
Choose between explicit currency selection for precise control or geolocation-based pricing to automatically show customers prices in their local currency. The anonymous-to-authenticated flow makes it easy to reduce friction in your signup process, letting users explore and configure their purchase before committing to creating an account. With support for multiple Plans, flexible pricing configurations, one-off and recurring billing, and automatic validation, you can build sophisticated checkout flows without managing the underlying complexity.
---
### Metered Usage
Source: https://beta.salable.app/docs/metered-usage
# Metered Usage
Fixed subscription fees don't work for every business model. When your costs scale with customer activity—API calls, storage, processing time—you need pricing that reflects actual consumption. Metered billing lets you charge for what customers use, aligning your revenue with the value you deliver.
## How Metered Billing Works
You define a **[Meter](/docs/core-concepts#meter-slug)** for each type of usage you want to track—`api_calls`, `storage_gb`, `messages_sent`. Meters belong to your organisation and can be reused across Plans at different rates: your Basic Plan might charge $0.01 per API call, while Pro charges $0.005.
When a customer subscribes to a Plan with a **[Metered Line Item](/docs/core-concepts#metered-line-item)**, Salable creates a **[Usage Record](/docs/core-concepts#usage-record)** to track their consumption. Throughout the billing period, you record usage via the API using the **[Owner](/docs/core-concepts#owner)** identifier and Meter slug. At period end, Salable finalises the count, calculates charges, and generates an Invoice.
```
Subscription Created
└─ Usage Record created (count: 0)
│
├─ You record usage → count increments
├─ You record usage → count increments
│
Billing Period Ends
└─ Usage Record finalised
└─ Charge calculated → Invoice generated
└─ New Usage Record created (count: 0)
```
The Owner is typically a user ID for individual Subscriptions, or an organisation/team ID for shared Subscriptions. Usage is tracked at the Owner level regardless of how many Grantees access the Subscription.
## Setting Up Metered Billing
### Adding Metered Line Items to Plans
You can create Meters inline while building Plans. In the Product editor, navigate to the Plan and click Add Line Item.
Fill in a descriptive Line Item Name, such as "API Usage". This appears on Stripe Invoices, so use customer-facing language. Leave Interval Type set to Recurring since metered charges repeat each billing cycle.
Under Pricing Type, select Metered. Choose your Billing Scheme: Per Unit multiplies the Price by the usage quantity, while Tiered offers volume-based or graduated pricing based on usage levels.
In Select Meter, choose an existing Meter or create one by typing the slug you want (like `api_calls` or `photo_generations`) and clicking Create. The Meter registers with Salable and Stripe immediately.
### Configuring Metered Pricing
After selecting your Meter, set up pricing for each billing Interval and Currency.
Click Add Price and select your Billing Interval (Month, Year, etc.). Click Add Currency and choose your Currency (USD, GBP, EUR, etc.).
For per-unit pricing, enter the Unit Amount as Price per unit. To charge $0.01 per API call, enter `0.01`.
For tiered pricing, configure each tier with its range and pricing: First Unit is auto-calculated from the previous tier, Last Unit is the upper limit or `inf` for the final tier, and Unit Amount is the Price per unit. Optionally add a Flat Amount as a base fee for reaching each tier.
**Example Per-Unit Pricing:**
```
Meter: api_calls
Price: 0.01 (= $0.01 per call)
Usage: 5,000 calls
Charge: 5,000 × $0.01 = $50.00
```
**Example Graduated Tiered Pricing:**
```
Tier 1: 1–10,000 calls at $0.01 each
Tier 2: 10,001–50,000 calls at $0.008 each
Tier 3: 50,001+ calls at $0.005 each
Usage: 60,000 calls
Charge: (10,000 × $0.01) + (40,000 × $0.008) + (10,000 × $0.005) = $470.00
```
## Recording Usage
### API: Record Usage
Record usage returns `204` immediately while processing happens in the background, so you can track millions of events without impacting your application's performance.
**Endpoint:** `POST /api/usage/record`
**Request Body:**
```json
{
"owner": "company_acme",
"meterSlug": "api_calls",
"increment": 50
}
```
**Parameters:**
- **owner**: identifier for the entity being charged (user ID, organisation ID, or team ID)
- **meterSlug**: Meter identifier to increment (must match a Meter in the customer's Subscription)
- **increment**: amount to add to the usage counter (minimum 1)
- **idempotencyKey** (optional): unique key to prevent duplicate increments; requests with the same key are deduplicated
**Response:**
Returns `204 No Content` on success. No response body. Absence of an error means success.
> **Note** The Usage Record must exist, which happens automatically when a customer subscribes to a Plan with that Metered Line Item. Recording usage for an `owner` and `meterSlug` combination without an active Subscription returns `404 Not Found`.
### Implementation Example
```javascript
async function recordUsage(owner, meterSlug, increment) {
const response = await fetch('https://api.salable.app/api/usage/record', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SALABLE_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ owner, meterSlug, increment })
});
if (!response.ok) throw new Error(`Failed to record usage: ${response.status}`);
}
// Record after processing a request
app.post('/api/analyze-image', async (req, res) => {
const result = await analyzeImage(req.body.imageUrl);
await recordUsage(req.user.organizationId, 'api_calls', 1);
res.json(result);
});
```
For high-volume scenarios, increment batches locally (every minute or every 100 calls), then record the accumulated total.
## Retrieving Usage Data
### API: List Usage Records
**Endpoint:** `GET /api/usage/count`
**Query Parameters:**
- `owner` (required): Owner identifier
- `meterSlug` (required): Meter slug to query
- `status` (required): One or more statuses, comma-separated (`recorded`, `current`, `final`)
- `before` (optional): Cursor for pagination (previous page)
- `after` (optional): Cursor for pagination (next page)
**Example Request:**
```bash
GET /api/usage-records?owner=company_acme&meterSlug=api_calls&status=current
```
**Example Response:**
```json
{
"type": "list",
"data": [
{
"id": "UsageRecord_01HXXX",
"organisation": "org_xxx",
"ownerId": "Owner_01HYYY",
"usageId": "Usage_01HZZZ",
"status": "current",
"count": 5432,
"recordedAt": null,
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-15T14:23:11Z"
}
],
"previousCursor": null,
"nextCursor": null,
"hasMore": false
}
```
**Response Fields:**
- **status**: record state (`recorded`, `current`, or `final`)
- **count**: accumulated usage for this period
- **recordedAt**: null for current records; set when finalised
Query with `status=current` for real-time usage dashboards, or `status=final` to retrieve historical billing periods. Results are sorted by creation date in ascending order; use `nextCursor` and `previousCursor` for pagination.
## Billing Cycle Behaviour
Metered usage operates on the Subscription's billing cycle. If a customer subscribes on January 15th with monthly billing, usage periods run January 15th to February 15th, then February 15th to March 15th.
At period end, Usage Records transition from `current` to `final` status. The accumulated count is used to calculate charges and generate an Invoice. The counter resets to zero, and a new Usage Record is created for the next period.
### Immediate Finalisation
Certain mid-cycle changes trigger immediate finalisation and a prorated Invoice:
- A Plan with Metered Line Items is removed from a Subscription
- A Subscription is cancelled immediately (not at period end)
- A Subscription's billing anchor changes
For end-of-period cancellation, usage accumulates normally until period end, then finalises on schedule.
---
You've now seen how to set up Meters, attach them to Plans with per-unit or tiered pricing, record usage via the API, and retrieve usage data for dashboards or historical analysis. Salable handles the billing cycle, finalisation, and Invoice generation automatically.
For more on configuring Products and pricing models, see the [Products & Pricing guide](/docs/products-and-pricing). For managing team access and understanding how Owners scope usage data, see [Grantees & Groups](/docs/grantee-groups).
---
### Dashboard & Admin
Source: https://beta.salable.app/docs/dashboard-and-admin
# Dashboard & Admin
## Overview
The Salable dashboard is your command center for managing subscription infrastructure without writing code. While the API provides programmatic access for your application, the dashboard gives you and your team a visual interface for configuring products, monitoring subscription health, responding to customer support requests, and understanding your revenue patterns.
The dashboard is designed around two key principles: you should be able to configure complex pricing models visually, and you should have immediate visibility into subscription status and customer issues. Whether you're setting up your first product, debugging a customer's payment problem, or analyzing which plans drive the most revenue, the dashboard provides the tools you need.
This guide walks you through the major areas of the dashboard, explaining not just what each section does but when and why you'd use it. Understanding these workflows helps you operate your subscription business efficiently and respond quickly to customer needs.
## Organization Management
Organizations are the top-level container for everything in Salable. All your products, plans, subscriptions, and settings are scoped to an organization. Understanding organization management is essential whether you're working solo or coordinating with a team.
### Creating and Switching Organizations
When you first sign up for Salable, you create or join an organization. Your account can belong to multiple organizations, which is useful if you manage several businesses or work with clients who each have their own Salable account.
The organization switcher appears in the dashboard sidebar, showing your current organization and providing access to others you belong to. Switching organizations is instantaneous—the entire dashboard updates to show the selected organization's data, settings, and resources.
This organizational separation ensures complete data isolation. You can't accidentally modify one business's products while viewing another's subscriptions. Each organization has its own API keys, Stripe connection, products, and customer base.
### Organization Settings
Organization settings control fundamental aspects of how your account operates. Access these settings through the sidebar to manage:
**Organization Name**
Configure your organization's name, which appears throughout the dashboard and in some customer-facing contexts. Choose a name that clearly identifies your business to avoid confusion when switching between multiple organizations.
**Team Management**
View and manage team members who can access your organization's data and settings.
**API Keys**
Access your API keys for both test and live modes, which your application uses to integrate with Salable.
**Webhook Endpoints**
Configure webhook endpoints where Salable sends real-time event notifications about subscription changes.
**Stripe Integration**
Manage your Stripe connection for processing payments in live mode.
### Team Management
Organizations support multiple team members with different roles and permissions. This collaboration structure lets your whole team access the tools they need without sharing personal login credentials.
Invite team members by entering their email address in the team management section. They receive an invitation email with a link to join your organization. If they don't already have a Salable account, they'll create one as part of accepting the invitation.
Team members can view and modify products and plans, access subscription data, manage customer support issues, and configure organization settings based on their permissions. This shared access streamlines operations—your customer support team can investigate subscription issues while your product team configures new pricing tiers, all without coordinating access to a single account.
When team members leave your organization, remove them from the team to revoke their access immediately. This ensures that former employees or contractors can no longer view customer data or modify your configuration.
## Test Mode and Live Mode
The dashboard operates in two distinct modes that mirror the API's test and live environments. Understanding how to use these modes effectively is crucial for safe development and reliable operations.
### Understanding Mode Separation
Test mode and live mode maintain completely separate data. Products created in test mode don't appear in live mode and vice versa. Subscriptions, customers, usage records, and all other data are similarly isolated.
This separation creates a safe sandbox where you can experiment with pricing models, test integration flows, and validate changes without affecting real customers or processing actual payments. Once you've thoroughly tested changes in test mode, you can replicate them in live mode with confidence.
The mode toggle appears prominently in the dashboard sidebar. The current mode is always visible, and switching modes is a single click. The entire dashboard updates to show the appropriate environment's data.
A visual indicator—typically a colored banner or badge—reminds you which mode you're in at all times. This prevents accidentally modifying production data when you intended to work in test mode, or wondering why changes aren't affecting real customers when you're viewing test data.
### Working in Test Mode
Use test mode extensively during initial setup, development, and when planning changes to your pricing structure:
**Product Configuration**
Create products and plans that mirror your intended live mode structure. This ensures your tests accurately represent production behavior.
**Checkout Testing**
Test the complete checkout flow using Stripe's test cards. Verify that payments process correctly and customers are redirected appropriately.
**Subscription Verification**
Create subscriptions and verify entitlement access works as expected. Check that grantees receive the correct permissions based on their subscription.
**Modification Testing**
Experiment with subscription modifications and cancellations. Test upgrades, downgrades, quantity changes, and the various proration options.
**Webhook Integration**
Test webhook delivery and your application's event handling. Ensure your application responds correctly to subscription events.
Test mode connects to Stripe's test environment, which accepts special test card numbers documented in Stripe's documentation. These test cards let you simulate successful payments, failed payments, authentication requirements, and various other scenarios without moving real money.
Changes you make in test mode never affect your live customers. This freedom to experiment is invaluable—you can try different pricing structures, test edge cases, and refine your subscription flows without any risk to your production environment.
### Transitioning to Live Mode
Once you've thoroughly tested your setup and integrated your application with the test mode API, you're ready to transition to live mode. This transition involves several steps that ensure a smooth launch.
First, switch to live mode in the dashboard. You'll notice that your product catalog is empty—remember, test and live mode data are completely separate. Recreate your products and plans in live mode, matching the structure you finalized in test mode. This manual recreation ensures you're intentional about what goes into production and gives you a chance to make any last-minute adjustments.
Update your application to use live mode API keys instead of test keys. This typically means changing environment variables or secrets management configuration. Verify that your application is pointing to live mode endpoints and handling live mode webhooks correctly.
Complete the Stripe onboarding process for your live mode organization. While test mode doesn't require full Stripe setup, live mode requires completed onboarding before you can process real payments. Follow the Stripe Connect flow accessible from the dashboard to provide your business information, banking details, and identity verification.
Test your first real subscription using a small amount or a test purchase by a team member before announcing to customers. This final verification ensures everything is wired correctly and working as expected in the production environment.
### Maintaining Both Environments
Even after launching in live mode, maintain your test mode environment as a staging area for future changes. When planning new features, pricing updates, or integration changes, prototype them in test mode first. This ongoing use of test mode as a development environment prevents production issues and maintains the safety of your experimentation space.
## Product and Plan Management
The product and plan configuration interface is where your pricing strategy takes shape. This visual editor lets you create complex pricing structures without writing code, though the underlying configuration is also available in YAML format for those who prefer infrastructure-as-code approaches.
### Product Creation
Access the products section from the dashboard sidebar to view your product catalog. In test mode, this starts empty. In live mode, it shows your active products and any archived products you've deprecated.
Creating a product begins with the **Create Product** button. The simplest flow involves entering a product name and clicking create—the product appears in your catalog immediately with default settings.
The product name should be clear and descriptive. This name appears in the dashboard, in API responses, and potentially in customer-facing areas depending on your integration. Choose names like "SaaS Platform" or "Analytics Add-on" rather than internal codes or abbreviations.
Once created, click the product to open the detailed configuration editor. This is where you define plans, configure settings, and build out your complete pricing structure.
### Product Settings Configuration
The product settings panel controls behavior that applies to all plans within the product. These settings provide sensible defaults for checkout and billing behavior while allowing per-checkout overrides through the API when needed.
**Checkout URLs** define where customers are redirected after the payment process. The success URL is where they land after successfully subscribing—typically your application's welcome page, onboarding flow, or dashboard. The cancel URL is where they return if they abandon checkout before completing payment—often your pricing page so they can reconsider their options.
These URLs can include query parameters to pass information back to your application. For example, `https://yourapp.com/welcome?plan=pro` lets your application know which plan the customer selected, even though the subscription information also arrives via webhook.
**Payment Collection Settings**
Control various aspects of the checkout experience:
- **Promotional Codes**: Enable to add a field where customers can enter discount codes you've created in Stripe
- **Automatic Tax**: Enable Stripe Tax for jurisdiction-appropriate tax calculation without manual configuration (requires opting in to Stripe Tax in your Stripe Dashboard)
- **Billing Address Collection**: Display billing address fields during checkout—required for tax calculation and useful for invoicing
- **Shipping Address Collection**: Display shipping address fields during checkout—useful if you're selling physical goods that require delivery
**Card Storage Preferences**
Determine how payment methods are saved for future use:
- **None**: Don't save payment methods. Customers must enter payment details for each purchase
- **Choice**: Let customers decide whether to save their card during checkout
- **Always**: Automatically save payment methods for streamlined future payments and subscription renewals
**Entitlement Access During Payment Issues**
The past due entitlements setting controls feature access when payments fail:
- **Enabled**: Customers retain access to their entitlements while Stripe attempts payment retry. This minimizes disruption for customers with temporary payment issues
- **Disabled**: Entitlements are immediately revoked when payment fails. This prevents providing service without current payment
The right choice depends on your business model and tolerance for providing service without guaranteed payment.
### Creating and Configuring Plans
Plans represent pricing tiers, add-ons, or options within a product. They can be subscription tiers or add-ons and plugins that customers purchase alongside a main plan. The plans section of the product editor shows existing plans and provides access to create new ones.
Click **Create Plan** to open the plan configuration interface. This guided flow walks you through the essential elements: plan name, trial period, tier tags, entitlements, and line items.
The plan name identifies the tier to customers. Use clear, marketing-friendly names like "Professional" or "Enterprise" rather than codes. This name appears in checkouts, invoices, and your customer portal.
Trial periods give customers free access for a specified number of days before charging them. Enter the number of days (between 1 and 730) or leave it blank for no trial. During trials, customers have full access to entitlements but aren't charged. If they cancel before the trial ends, they're never billed.
[Tier Tags](/docs/core-concepts#tier-tags-and-tier-sets) make plans mutually exclusive by grouping them into tier sets. When you assign the same tier tag to multiple plans, an owner can only subscribe to one of those plans at a time. This is useful for subscription tiers where customers should choose one option (Basic, Pro, or Enterprise) rather than subscribing to multiple. Enter the tier tag string in the Tier Tag field, plans that share the same value belong to the same tier set.
Entitlement selection determines which features customers on this plan can access. The typeahead input lets you search for existing entitlements or create new ones inline. Simply type the entitlement name and click **Create** if it doesn't exist yet. The entitlement naming convention is lowercase with underscores, like `advanced_analytics` or `unlimited_exports`.
### Line Item Configuration
Line items define the actual charges within a plan. This is where pricing structure becomes concrete—flat fees, per-seat charges, usage-based billing, or combinations of these models.
Click **Add Line Item** to open the line item editor. Start by selecting the line item type: flat rate for fixed recurring fees, per-seat for team-based pricing, metered for usage-based billing, or one-time for setup fees and single charges.
**For flat rate line items**, configure the billing interval (monthly, yearly, or custom intervals), the price in each currency you support, and optionally any tiered pricing if you want volume-based discounts even for flat fees.
**For per-seat line items**, you have additional configuration options. The min seats setting enforces a minimum purchase quantity—useful for team plans where you want at least 5 seats per subscription. The max seats setting caps the maximum quantity, which can help you identify when customers might need enterprise agreements. Variable seating allows customers to choose their quantity within these bounds, while fixed seating sells plans with a predetermined number of seats.
Tiered per-seat pricing lets you offer volume discounts. Configure tiers with breakpoints like 1–10 seats at $10 each, 11–50 seats at $8 each, and 51+ seats at $6 each. Customers automatically get the appropriate tier pricing based on their quantity.
**For metered line items**, configure the slug that uniquely identifies this usage type, the price per unit of usage, and the aggregation method. The slug is critical—it's what you provide when recording usage through the API, and it must be unique across your organization. Use descriptive slugs like `api-calls` or `storage-gb` rather than generic names.
Each line item can have prices for multiple billing intervals and currencies. The editor shows tabs or sections for monthly, yearly, and custom intervals. Within each interval, specify prices in multiple currencies with one marked as default.
### YAML Import and Export
For teams who prefer infrastructure-as-code approaches or need to replicate products across environments, the dashboard supports YAML import and export.
Click **Export to YAML** on any product to download a complete configuration file including all plans, line items, prices, and settings. This YAML file is version-controllable, shareable with team members for review, and serves as documentation of your pricing structure.
The **Import from YAML** feature lets you upload a configuration file to create or update products. This is particularly useful when transitioning from test mode to live mode—export your tested configuration, review it, and import it into live mode rather than manually recreating everything.
YAML import validates the configuration before applying changes, catching errors like missing required fields, invalid price configurations, or reference to non-existent entitlements. If validation fails, error messages indicate what needs to be fixed.
### Managing Entitlements
The entitlements section, accessible from the sidebar, shows all entitlements across your organization. This global view helps you understand your complete feature access structure and manage entitlements that might be shared across multiple products and plans.
Create standalone entitlements here if you prefer to define your entitlement structure before building products, or manage entitlements that were created inline during plan configuration.
Each entitlement has a name (the slug used in API checks), a description for team reference, and optionally an expiry date if the entitlement should automatically revoke after a certain date. The interface shows which plans include each entitlement, making it easy to audit feature access across your pricing tiers.
Archive entitlements you no longer use rather than deleting them. Archived entitlements don't appear in plan configuration interfaces but remain in the system so historical data remains consistent. If subscriptions still reference an archived entitlement, those customers retain access until their subscription changes.
## Subscription Management
The subscriptions section gives you operational visibility into your customer base. This is where you monitor subscription health, investigate customer issues, and make administrative changes when needed.
### Subscription List and Filtering
The subscriptions list shows all subscriptions in the current mode, with powerful filtering and search capabilities to help you find relevant subscriptions quickly.
The default view shows active subscriptions sorted by creation date, but you can customize this view extensively. Filter by status (active, past_due, canceled, trialing) to focus on specific subscription states. Search by customer email or owner ID to find a specific customer's subscriptions. Filter by product or plan to see all customers on a particular tier. Sort by various fields like creation date, next billing date, or subscription value.
These filters combine, so you can answer questions like "show me all past_due subscriptions for the Pro plan" or "find all subscriptions created in the last week." This flexibility is essential for support operations and revenue analysis.
Each subscription in the list shows key information at a glance: the customer's email or owner ID, current status, subscription plans, and the next billing date. Click any subscription to open the detailed view.
### Subscription Detail View
The subscription detail page provides comprehensive information about a single subscription and tools for making modifications or resolving issues.
The summary section shows the customer information including owner ID and contact details if available, current subscription status and any scheduled changes (like upcoming cancellation), creation date and subscription age, and the next billing date and amount.
The subscription items section lists all plans included in the subscription. For each item, you see the plan name and details, quantity (for per-seat plans), associated grantee group if applicable, and the price being charged per interval.
The billing history section shows all invoices for this subscription. Each invoice entry displays the billing date, the amount charged, payment status (paid, failed, or pending), and a link to download the PDF receipt. This history is invaluable for resolving billing questions or investigating payment issues.
For subscriptions with metered line items, the usage section shows current period usage. You can see recorded usage quantities, the calculated charges based on usage, and historical usage from previous billing periods.
### Making Subscription Modifications
The subscription detail page includes actions for modifying the subscription. These administrative tools let you handle customer requests or resolve issues without requiring API calls.
**Change Plan** opens an interface for upgrading or downgrading the customer. Select the new plan, choose the proration behavior (immediate charge, end of period change, or no proration), and apply the change. The interface shows a preview of any prorated charges before you confirm.
**Adjust Quantity** modifies the seat count for per-seat plans. Enter the new quantity, see the prorated amount that will be charged immediately, and confirm the change. The system enforces minimum and maximum seat limits configured on the line item.
**Add Plans** lets you attach additional plans to the subscription—useful when customers want to add features or add-ons. Select the plan to add, specify the quantity, and choose proration behavior. The new plan becomes part of the subscription and bills on the same cycle.
**Cancel Subscription** offers both end-of-period and immediate cancellation options. End-of-period cancellation schedules termination for the current billing period's end, allowing the customer to retain access for the time they've paid for. Immediate cancellation terminates the subscription right away, with optional prorated refunds.
These administrative actions are the same operations available through the API, but the dashboard interface provides convenience and visual feedback that's helpful for support scenarios.
### Subscription Support Tools
When customers contact support with subscription issues, the dashboard provides tools for investigation and resolution.
The **Payment Method** section shows the customer's current payment method (last four digits and card type) and provides a link to Stripe's billing portal where customers can update their payment information. You can send this portal link to customers who need to update expired cards or change payment methods.
The **Event Log** shows a history of all subscription events, including creation, modifications, payment attempts, and status changes. This timeline helps you understand exactly what happened and when, which is essential for troubleshooting.
If a subscription is in past_due status due to payment failure, the dashboard shows the failure reason (insufficient funds, card expired, authentication required, etc.) and the retry schedule. You can see when Stripe will attempt payment again and manually trigger retry attempts if the customer has fixed the issue.
For subscriptions scheduled for cancellation, you can reverse the cancellation through the **Resume Subscription** action. This restores normal auto-renewal if the customer changed their mind.
## Analytics and Reporting
The analytics section provides visibility into your subscription business metrics, helping you understand revenue trends, identify successful pricing strategies, and spot potential issues.
### Revenue Overview
The revenue dashboard shows your monthly recurring revenue (MRR) and annual recurring revenue (ARR) based on active subscriptions. These metrics exclude one-time charges and usage-based fees, focusing on predictable recurring revenue from subscription plans.
Trend charts show how MRR evolves over time, helping you identify growth patterns or concerning declines. Segmentation breaks down MRR by product, plan, or customer cohort, revealing which pricing tiers drive the most revenue.
New MRR, expansion MRR, contraction MRR, and churned MRR components help you understand the drivers behind revenue changes. New MRR comes from new subscriptions. Expansion MRR comes from upgrades and increased quantities. Contraction MRR results from downgrades. Churned MRR is lost from cancellations.
### Subscription Metrics
Beyond revenue, subscription health metrics provide operational insight. Active subscriptions show your customer base size. Churn rate indicates what percentage of customers cancel each month. Average revenue per account (ARPA) shows the typical customer value.
These metrics broken down by plan or cohort reveal patterns. If your Pro plan has much lower churn than your Basic plan, that suggests Pro customers find more value in your product. If newer cohorts have higher churn than older ones, that might indicate onboarding or product issues.
The subscription lifecycle view shows how many subscriptions are in each state at any given time. A high number of past_due subscriptions suggests payment issues that need investigation. A growing number of trialing subscriptions indicates healthy top-of-funnel activity.
### Customer Insights
Customer analytics help you understand who your customers are and how they use your product. Geographic distribution shows where customers are located, which informs currency support and localization priorities. Plan distribution reveals which tiers are most popular. Lifetime value estimates help you understand customer economics.
The cohort analysis view groups customers by when they subscribed and tracks their behavior over time. This helps answer questions like "do customers who subscribed in January have different retention patterns than those who subscribed in June?" or "how long does it take for the average customer to upgrade to a higher tier?"
### Payment Analytics
Payment success rates and failure reasons help you optimize billing operations. If you see a high rate of "card expired" failures, proactive expiration reminders might help. If "insufficient funds" failures spike at certain times of month, you might adjust billing dates.
Geographic payment success varies significantly. Understanding where you experience payment friction helps you decide whether to support additional payment methods or make other localization improvements.
### Exporting Data
All analytics views support data export to CSV or JSON for deeper analysis in external tools. Export filtered subscription lists for bulk operations, invoice data for accounting reconciliation, usage data for customer reporting, or complete datasets for custom analytics in your data warehouse.
These exports respect the current mode (test or live) and any filters you've applied, giving you precise control over what data you extract.
## Integration Management
The integrations section manages connections to external services, most importantly your Stripe account.
### Stripe Integration
Salable uses Stripe for payment processing, which means connecting your Stripe account is essential for accepting real payments in live mode.
Click **Connect Stripe** to begin the Stripe Connect onboarding flow. You'll provide your business information including legal business name, address, and entity type. Add banking information where Stripe should deposit your funds. Complete identity verification by providing identification documents as required by your jurisdiction. Review and submit your application for Stripe to process.
This onboarding process is required only in live mode. Test mode doesn't need Stripe connection because it uses Stripe's test environment automatically.
Once connected, the integrations page shows your Stripe account status and provides a link to your Stripe Dashboard where you can view detailed payment information, configure Stripe-specific settings, manage disputes and refunds, and access Stripe's reporting tools.
The integration status indicator shows whether your Stripe onboarding is complete. You can create products and plans with minimal Stripe setup—even if your account shows as incomplete. To generate checkout links in test mode, you need to complete the business type and personal details forms in Stripe's onboarding. For live mode checkout links that process real payments, you must complete full onboarding with Active status.
### Webhook Configuration
Configure webhook endpoints to receive real-time notifications of subscription events. The webhook configuration interface lets you add multiple endpoints, each with its own URL and event type selection.
Enter the webhook URL where Salable should send events. This must be a publicly accessible HTTPS endpoint. Select which event types this endpoint should receive—you might send all events to one endpoint or route different event types to different handlers.
After creating a webhook endpoint, the dashboard shows the signing secret you need to verify webhook authenticity. Copy this secret into your application's environment configuration.
The webhook detail view shows recent deliveries, including which events were sent, whether delivery succeeded or failed, and the full request and response. This delivery history is invaluable for debugging webhook issues. If deliveries are failing, you can see the exact error and manually retry individual webhooks.
Test your webhook endpoints using the **Send Test Event** button. This triggers a sample event delivery without actually creating the associated resource, letting you verify your endpoint is working correctly before real events occur.
## User Settings and Preferences
Your personal account settings are separate from organization settings and control your individual dashboard experience.
### Profile Information
Update your personal information including name, email address, password, and notification preferences. Your email address is used for account communications, password resets, and optional product updates.
Enable or disable email notifications for specific events. You might want notifications for payment failures and cancellations but not for every successful payment. Customize these preferences to stay informed without being overwhelmed.
### Security Settings
Enable two-factor authentication (2FA) for additional account security. This requires a second verification step beyond your password when logging in, typically through an authenticator app on your phone.
View active sessions to see where your account is currently logged in. Revoke sessions from the list if you notice unfamiliar locations or want to force re-authentication on all devices.
### API Keys and Access Tokens
While organization-level API keys are managed in organization settings, your personal access tokens for dashboard API usage appear in your user settings. These tokens are useful for scripting dashboard operations or building custom tooling.
Generate personal access tokens with appropriate scopes, name them descriptively to remember their purpose, and revoke them when no longer needed. Treat personal access tokens like passwords—store them securely and never commit them to version control.
## Support and Documentation Access
The dashboard includes integrated support resources to help you quickly resolve issues and learn about features.
### In-App Help
Most dashboard sections include contextual help explaining what the section does and how to use it. Look for info icons or help links near complex features to access this guidance without leaving your workflow.
The command palette (typically opened with Cmd+K or Ctrl+K) provides quick access to common actions, navigation to any section, and searching documentation. This is the fastest way to navigate the dashboard once you're familiar with it.
### Support Contact
If you can't resolve an issue through documentation, the support link in the dashboard opens a contact form where you can describe your problem and include relevant details. Support requests automatically include your organization ID and current mode, helping support staff investigate issues faster.
For urgent issues affecting production systems, clearly indicate urgency in your request. Response times vary by support tier, with critical production issues receiving priority attention.
### Status and Changelog
The status page (linked from the dashboard) shows current system status and any ongoing incidents. If you're experiencing unexpected behavior, check the status page first to see if there's a known issue affecting multiple customers.
The changelog announces new features, improvements, and important changes to the platform. Subscribe to changelog updates to stay informed about new capabilities you can leverage in your subscription business.
---
The dashboard transforms the complexity of subscription management into a visual, intuitive interface that complements the API's programmatic power. Whether you're configuring your initial pricing structure, investigating a customer support issue, or analyzing revenue trends, the dashboard provides the tools and visibility you need to operate your subscription business effectively. Combined with the API for automated operations and the comprehensive documentation for understanding concepts, you have a complete toolkit for building and managing sophisticated subscription infrastructure.
---
### Testing & Development
Source: https://beta.salable.app/docs/testing-and-development
# Testing & Development
## Overview
Ensuring your Salable payment model is setup appropriately and working as intended is crucial before handling real payments from customers. Salable's [Test Mode](/docs/core-concepts#test-mode-vs-live-mode) provides a complete sandbox environment where you can experiment freely, validate your Payment Integration, and test your Subscription flows without any risk to customer experience.
Test Mode isn't just a checkbox or flag—it's a fully functional parallel environment with its own Products, Subscriptions, API keys, and data. With Test Mode you can develop new features, test edge cases, or validate changes with confidence. Test Mode also integrates with [Stripe's test infrastructure](https://docs.stripe.com/testing-use-cases) for realistic payment simulation.
This guide walks you through effective testing strategies, from initial integration development through ongoing feature work and eventual production deployment. Understanding how to leverage Test Mode fully accelerates your development cycle and prevents production issues that can erode customer trust.
## Test Mode Fundamentals
### Complete Environment Isolation
Remember, Test Mode and Live Mode are entirely separate environments. When you create a Product in Test Mode, it exists only in Test Mode—it doesn't appear in Live Mode. This isolation extends to every resource: Plans, Subscriptions, Carts, Usage Records, Entitlements, Grantee Groups etc.
You can create test Products with experimental pricing, simulate Subscription lifecycles, test edge cases that would be difficult to reproduce in production, and validate integration changes without any possibility of affecting real customers.
The only difference between Test Mode and Live Mode is that Test Mode doesn't process real money. All payments use Stripe's test environment, thus official payments are never executed.
> **Note** Test Mode API keys can only access Test Mode data. Additionally Webhooks are also segregated, with test events sent to test webhook endpoints and production events sent to production endpoints.
### Stripe Requirements
Both Test and Live Mode require a Stripe account connected through Payment Integrations. Once connected, you can build your pricing structure—creating Products, configuring Plans, and setting up Line Items—even with minimal Stripe account setup.
To generate checkout links in Test Mode, you must complete two forms in the Stripe Connect onboarding process: the **business type** form and the **personal details** form. Without completing both forms, Stripe will reject checkout link generation with an error. You don't need to complete full onboarding (banking details, identity verification) for Test Mode checkout links to work.
Live Mode however requires the full Stripe Connect onboarding process to be completed. While you can create Products and Plans with incomplete onboarding, generating functional checkout links requires your Stripe account to be fully onboarded with **Active** status, including banking information and identity verification.
Stripe's test environment accepts special test card numbers that simulate different scenarios. The card number `4242 4242 4242 4242` always succeeds. The number `4000 0000 0000 0002` always declines. Other test cards simulate specific scenarios like authentication requirements, insufficient funds, or expired cards. [Read about Stripe test environments here](https://docs.stripe.com/testing-use-cases).
## Setting Up Your Test Environment
### Initial Configuration
Begin by ensuring you're in Test Mode—check the mode indicator in the dashboard sidebar. If you're in Live Mode, switch to Test Mode before proceeding.
Start by creating your Product(s) how you would intend to on Live Mode. This may be a single Product if you're testing a simple Subscription model, or multiple Products if you offer different services or add-ons.
Next configure Plans for your test Products with the pricing models you have in mind. For example a Basic Plan at $29/month, a Pro Plan at $79/month, and a Premium Plan at $99/month. Create and include the same Entitlements you'll use in production so you can test feature access correctly.
Ensure your test data is accurate to make sure your are simulating a production level environment.
### Test API Keys
Generate test API keys from the organization settings in the dashboard. These keys grant access only to Test Mode data.
Store test API keys in your development environment's configuration, typically as environment variables. Use separate configuration for development, staging, and production environments, ensuring that each environment uses appropriate API keys and points to the correct Salable endpoints.
```bash
# .env.development
SALABLE_API_KEY=test_abc123...
SALABLE_BASE_URL=https://api.salable.app/test
# .env.production
SALABLE_API_KEY=live_xyz789...
SALABLE_BASE_URL=https://api.salable.app/live
```
This configuration separation prevents accidentally using production keys during development or test keys in production.
### Webhook Configuration for Testing
You can configure test webhook endpoints that point to your development or staging servers. For local development, hosting tools such as [ngrok](https://ngrok.com/) provide public URLs that forward to your local development server, allowing Salable to deliver webhooks to your development environment.
```bash
# Start ngrok to expose local port 3000
ngrok http 3000
# Use the ngrok URL as your test webhook endpoint
# Example: https://abc123.ngrok.io/webhooks/salable
```
Configure this ngrok URL as a test webhook endpoint in the Salable dashboard. Select the event types you want to receive, and save the signing secret for verifying webhook authenticity.
As you develop your webhook handler, the dashboard's webhook delivery log shows each event sent to your endpoint, the payload, your endpoint's response, and whether delivery succeeded. This visibility accelerates webhook development by making it easy to see what's being sent and how your endpoint responded.
## Testing Checkout Flows
### Basic Subscription Purchase
The fundamental flow to test is a customer successfully subscribing to a Plan. This validates that your entire checkout integration works end-to-end.
Start by creating a Cart in your application using your test API key. Add a Plan to the Cart and generate a checkout link athennd navigate to it in your browser.
Stripe's checkout page loads in Test Mode, indicated by a "Test Mode" badge. Enter test payment information using Stripe test cards. Use the card number `4242 4242 4242 4242`, any future expiry date, any CVC, and any postal code.
Complete the checkout flow by clicking the payment button. Stripe processes the test payment instantly and redirects you to your configured success URL. Shortly after, your webhook endpoint receives a `Subscription.created` event.
Verify the Subscription appears in the Salable dashboard under Subscriptions. Check that your application provisioned access correctly—the customer should have the Entitlements associated with their Plan.
### Testing Payment Failures
Payment failures are inevitable and it is important to handle these events gracefully. Stripe also provides test card data that fails or declines payments and can be used to genereate test payment failure events.
Use the card number `4000 0000 0000 0002` which always declines with a generic decline code. The error message will indicate that the card has declined.
Use the card number `4000 0000 0000 9995` which declines with an "insufficient funds" error.
Test authentication requirements using the card number `4000 0025 0000 3155` which requires 3D Secure authentication. Complete the authentication challenge in the test flow to verify your checkout process handles authentication correctly.
### Testing Different Currencies
If you intend to support multiple currencies, it is recommended to test the checkout flow with each one. Create Carts specifying different currencies and verify that checkout displays amounts correctly, Stripe processes payments in the expected currency, and Subscriptions are created with correct pricing.
### Team Subscription Checkout
For applications with team-based Subscriptions, test the complete team onboarding flow. Create a Grantee Group and add members to it. Next create a Cart with a per-seat Plan, assign the Grantee Group to the Cart item with the appropriate quantity.
Complete checkout and verify that all team members receive appropriate Entitlements. Test adding members after Subscription creation to validate seat management works correctly.
## Testing Subscription Management
### Upgrade and Downgrade Flows
We can also test Plan upgrades and downgrades with the test Subscriptions we have just created. In your application's Subscription management interface, trigger a Plan upgrade or downgrade from Basic to Pro. Verify that the API request succeeds, the necessary proration charges are calculated correctly, Entitlements update immediately to reflect the new Plan, and your application UI updates to show the new Plan.
### Quantity Adjustments
For per-seat Plans, you should test increasing and decreasing seat quantities. Verify that quantity increases succeed and charge prorated amounts, quantity decreases succeed but respect minimum seat requirements, attempts to reduce below minimum seats are rejected appropriately, and Grantee Group size constraints are enforced if applicable.
### Adding and Removing Plans
Test adding add-ons or additional Products to existing Subscriptions. Create another Subscription for one of your test Products, then use the Subscription modification API to add an add-on or another Product. Verify the new Plan appears on the Subscription, is included in invoices, and grants its Entitlements to the customer.
Test removing Plans by selecting a Subscription item and deleting it. Verify that the Plan is removed, associated Entitlements are revoked, and the customer isn't charged for the removed Plan in future billing cycles. Verify that attempts to remove the last Plan from a Subscription are rejected—Subscriptions must always have at least one Plan.
### Cancellation Testing
Test both types of cancellation to verify your implementation handles each correctly. Cancel a Subscription with end-of-period timing and verify that it remains active until the billing period ends, the customer retains access until the scheduled cancellation date, no refund is issued, and your application shows the upcoming cancellation to the customer.
Test immediate cancellation and verify that the Subscription terminates right away, Entitlements are revoked immediately, a prorated refund is issued if configured, and your application handles the sudden access revocation appropriately.
Test reversing scheduled cancellations by resuming a Subscription after scheduling end-of-period cancellation. Verify that the Subscription continues to auto-renew normally.
## Testing Metered Usage
### Recording Usage
If you have metered Line Items you can test that the usage is being recorded correctly. In your application, trigger actions that should record usage—making API calls, consuming storage, or whatever your metered metric is.
Verify that usage recording API calls succeed, the correct quantity is recorded, and the slug correctly identifies the usage type. Test idempotency by sending the same slug multiple times for the same owner in the same billing period—verify that the quantity updates rather than accumulating.
Test usage recording across multiple billing periods. Record usage, wait for the Subscription to bill (or manually advance time in your test if you're not actually waiting days), and verify that usage records finalize and appear on the invoice.
### Checking Current Usage
Test your application's usage display by retrieving current usage counts through the API and displaying them to customers. Verify that counts are accurate, update in real-time as usage is recorded, and reset appropriately at the start of new billing periods.
If your application enforces usage limits, test that these limits work correctly. Record usage up to the limit and verify that your application prevents further usage or prompts the customer to upgrade.
### Cross-Plan Metering
If you use the same metered slug across multiple Plans (such as shared API call tracking for Basic and Pro Plans at different per-unit prices), verify that usage recorded against one slug appears correctly on invoices for Subscriptions with different Plans, each customer is charged at their Plan's rate, and a single usage counter tracks consumption regardless of which Plan the customer has.
This shared metering is powerful but requires careful testing to ensure billing calculates correctly.
## Testing Entitlements and Access Control
### Entitlement Checking
It is crucial to test that your customers have access to features through the corresponding Entitlements as intended. In your application, call the Entitlement check API with a Grantee ID and Entitlement name, then verify that the response accurately reflects the user's Subscription status.
Test positive cases where users should have access—verify that subscribed users with the appropriate Plan receive positive Entitlement checks. Test negative cases where users shouldn't have access—verify that unsubscribed users, users on Plans without the Entitlement, and users with canceled Subscriptions receive negative Entitlement checks.
Test timing boundaries by checking Entitlements immediately after Subscription creation, right before Subscription expiry, immediately after cancellation, and during trial periods. These boundary conditions surface timing bugs that might grant or deny access incorrectly.
### Grantee Group Access
For team Subscriptions, test that all members of a Grantee Group receive Entitlements from the group's Subscription. Add a user to a group associated with a Subscription, check that user's Entitlements, and verify they have access to features granted by the group Subscription.
Test removing users from groups and verify that their Entitlements update appropriately. If they have no other Subscriptions granting those Entitlements, they should lose access immediately.
Test users who belong to multiple Grantee Groups with different Subscriptions. Verify that they receive the union of Entitlements from all groups—if one group Subscription grants `feature_a` and another grants `feature_b`, the user should have both.
### Entitlement Expiry
If you use Entitlements with expiry dates, test that access is correctly granted before expiry and denied after. This typically involves creating Entitlements with expiry dates in the near future, checking access before and after the expiry timestamp.
## Testing Webhook Handling
### Event Processing
Your webhook handler needs to process various event types correctly. Trigger each important event type in Test Mode and verify your handler processes it appropriately.
Create Subscriptions to trigger `subscription.created` events. Verify your handler provisions access, updates your database, or performs whatever initialization your application requires for new Subscriptions.
Modify Subscriptions to trigger `subscription.updated` events. Verify your handler detects the changes and updates your application state accordingly.
Cancel Subscriptions to trigger `subscription.canceled` events. Verify your handler revokes access and updates Subscription status in your system.
Use Stripe test cards that fail to trigger `payment.failed` events. Verify your handler notifies customers and tracks the failure appropriately.
### Idempotency Testing
Webhook deliveries can be duplicated, so your webhook handlers must be idempotent. Test this by manually resending the same webhook event multiple times through the dashboard's webhook delivery interface.
Verify that processing the same event multiple times has the same effect as processing it once—no duplicate records are created, no double provisioning occurs, and no multiple notifications are sent.
The event ID included in webhook payloads is your tool for implementing idempotency. Store processed event IDs and skip events you've already seen.
### Error Handling
Test how your webhook handler behaves when processing fails. Introduce errors in your handler code (perhaps by commenting out database access temporarily) and observe webhook deliveries failing.
Verify that Salable retries failed deliveries according to the documented retry schedule. Check the dashboard's webhook delivery log to see retry attempts. Fix the error in your handler and verify that a subsequent retry succeeds.
This testing ensures your production system can recover from transient issues without losing events.
### Webhook Security
Verify that your webhook handler correctly validates signatures. Send a webhook with an invalid signature (modify the signature header before processing) and verify that your handler rejects it with a 401 response.
Send a webhook with no signature and verify rejection. Send a webhook with the correct signature and verify acceptance. This security testing ensures production webhooks can't be forged by malicious actors.
## Performance and Load Testing
### Rate Limit Verification
While developing, verify that your application handles rate limits gracefully. Intentionally send more than 100 requests per second to the API and observe the 429 responses. Verify that your client implements exponential backoff and retries appropriately.
Test that your application continues to function correctly under rate limiting—requests eventually succeed, users don't see errors, and your system doesn't enter a retry storm that makes the situation worse.
### Concurrent Operations
Test scenarios where multiple operations happen simultaneously. Create several Carts concurrently, modify the same Subscription from multiple requests, record usage from multiple sources simultaneously, and check Entitlements for many users in parallel.
Verify that your application handles concurrency correctly without race conditions, duplicates, or other issues. Database transactions, idempotency keys, and proper error handling become important here.
### Large Dataset Handling
If you expect to manage large amounts of Subscriptions or high usage volumes, make sure to test with realistic data sizes. Create dozens or hundreds of test Subscriptions, record large volumes of usage, and retrieve large Subscription lists with pagination.
Verify that pagination works correctly, your application doesn't load excessive data into memory, and performance remains acceptable with realistic data volumes.
## Integration Testing Strategies
### End-to-End Test Suites
Build automated test suites that exercise complete flows from your application through Salable to Stripe and back. These tests give you confidence that the entire system works together correctly.
An end-to-end Subscription test might programmatically create a Cart, add a Plan, generate a checkout link, simulate completing checkout (using test cards), wait for the webhook, verify the Subscription was created, check Entitlements, and verify your application's state is correct.
### Continuous Integration
Integrate your test suite into your continuous integration pipeline. Configure your CI environment with Test Mode API keys, run the full test suite on every commit or pull request, and block deployments if tests fail.
## Transitioning to Production
### Pre-Launch Checklist
Before launching in Live Mode, verify your test mode data is accurate. Confirm your Test Mode integration works completely with all necessary flows tested, including success paths, error handling, edge cases, and webhook processing. Ensure your Stripe Connect connection is established in Live Mode and, critically, that your Stripe Connect onboarding is fully complete with an **Active** status—incomplete Stripe Connect accounts will prevent checkout links from working.
Once you are satisfied that your test Products are accurate, you can simply navigate to the Product configuration page and click the Copy to Live Mode button. This will copy over your Product and all corresponding data (Plans, Line Items etc) over to Live Mode.
Make sure to update your application configuration to use Live Mode API keys and endpoints. Configure production webhook endpoints with appropriate URLs and verify they're reachable.
### Gradual Rollout
You may want to consider launching to a limited audience initially rather than opening to all customers immediately. This limited rollout lets you verify production behavior with real customers and real money while limiting the impact of any undiscovered issues.
Monitor error rates, webhook delivery success, payment success rates, and customer feedback during initial rollout. If everything looks good, expand access to your full customer base.
### Production Monitoring
Once live, it is best to monitor key metrics continuously. Track Subscription creation rates and success/failure patterns, payment success rates and decline reasons, webhook delivery success and processing times, Entitlement check response times, and error rates across all API operations.
Set up alerts for anomalies like sudden spikes in payment failures, webhook delivery failures, API error rates, or unexpected drops in Subscription creation. These alerts let you respond quickly to issues before they affect many customers.
## Common Testing Scenarios
### Free Trial to Paid Conversion
Test the complete trial lifecycle by creating a Plan with a trial period, subscribing a test customer, verifying they have full access during the trial, simulating time passing (or noting when the trial will end), and verifying that billing occurs correctly when the trial ends.
Test trial cancellation by canceling before the trial ends and verifying the customer is never charged.
### Team Onboarding Flows
Test the complete team signup journey where an administrator creates an account, invites team members before purchasing, subscribes to a team Plan with appropriate seat quantity, and all team members receive access immediately.
Test team management after purchase by adding members, adjusting seat quantities, removing members, and verifying Entitlements update correctly for all team members.
### Complex Multi-Plan Subscriptions
For customers who purchase multiple Products or add-ons together, test creating Carts with multiple Plans, completing checkout, verifying all Plans appear on the Subscription, and checking that all Entitlements from all Plans are granted.
Test modifying these multi-Plan Subscriptions by adding additional Plans, removing some Plans while keeping others, and changing quantities on specific Plans.
### Migration from Other Systems
If you're migrating existing customers from another billing system to Salable, test your migration process thoroughly. Create test data representing your existing customer structures, run your migration scripts against Test Mode, verify Subscriptions are created correctly with proper billing dates, and ensure Entitlements map correctly from old system to new.
## Debugging and Troubleshooting
### API Response Inspection
When tests fail or behavior doesn't match expectations, examine API responses carefully. The detailed error messages include error codes for programmatic handling, human-readable messages explaining what went wrong, and details about validation failures or constraint violations.
Enable detailed logging in your application to capture all API requests and responses during development.
### Dashboard Investigation
Use the Salable dashboard to investigate issues from a different angle. View the resources your API calls created or modified, check Subscription states and histories, review webhook delivery logs and event payloads, and examine usage records and invoice Line Items.
The dashboard provides visibility that complements API logs, often making issues obvious that would be opaque looking only at API request/response logs.
### Webhook Replay
When webhook handling issues occur, use the dashboard's webhook replay feature to resend events to your handler. This lets you fix bugs in your handler and reprocess events without recreating entire Subscription scenarios.
The replay preserves the original event ID, so make sure your idempotency checking allows for replays during development—you might need to clear your processed events table or add a development mode bypass.
### Support Resources
If you encounter issues you can't resolve, Salable support can help. When contacting support, include relevant Subscription IDs, API request/response details including full JSON payloads, webhook event IDs and payloads if applicable, and descriptions of what you expected versus what actually happened.
This context helps support staff diagnose issues quickly and provide specific solutions.
---
Thorough testing in Test Mode is your foundation for reliable Subscription management. By methodically testing checkout flows, Subscription management, usage tracking, Entitlements, and webhooks before launching, you prevent production issues that erode customer trust and create support burden. The investment in comprehensive testing pays dividends through smooth production operations, confident deployments, and customer experiences that work correctly the first time.
---
### Caching Strategies for Entitlements
Source: https://beta.salable.app/docs/caching-strategies-for-entitlements
# Caching Strategies for Entitlements
## Overview
Implementing effective caching for entitlement checks is crucial for building performant applications. This guide covers caching strategies specifically for Node.js and Next.js applications, helping you balance performance with data accuracy.
**Key benefits of caching:**
- Reduced API calls and faster response times
- Lower latency for feature access checks
- Better user experience during high traffic
- Reduced load on Salable's API
**Trade-offs to consider:**
- Cached data may be stale during subscription changes
- Cache invalidation adds complexity
- Memory usage for cache storage
- Consistency across distributed systems
## When to Cache
### Good Candidates for Caching
- **Frequent entitlement checks**: Features checked on every page load or API request
- **Stable subscriptions**: Long-term subscriptions that rarely change
- **Read-heavy patterns**: More reads than subscription updates
- **Session-based access**: User sessions with consistent subscription status
### Poor Candidates for Caching
- **Critical security checks**: Financial transactions, admin access, sensitive operations
- **Real-time subscription changes**: Immediately after upgrade/downgrade flows
- **Background jobs**: Batch processes that can tolerate API latency
- **Infrequent checks**: Features checked rarely don't benefit from caching
## Recommended Cache Durations
Choose cache TTL (Time-To-Live) based on your application's needs:
| Use Case | Recommended TTL | Rationale |
| --------------------- | --------------- | ------------------------------------------ |
| Short-lived sessions | 5–10 minutes | Quick invalidation for logged-out users |
| Long-lived sessions | 15–30 minutes | Balance between performance and freshness |
| Background jobs | No cache | Always check fresh data |
| Critical features | 2–5 minutes | Shorter TTL for important access decisions |
| Non-critical features | 30–60 minutes | Longer TTL for less sensitive features |
**Consider your billing cycle:**
- Monthly billing: Longer cache TTLs are acceptable (15–30 minutes)
- Usage-based billing: Shorter TTLs to reflect consumption changes (5–10 minutes)
- Perpetual licence: cache for the lifetime of the app
## Backend/API Implementation Example
Here's a simple in-memory caching pattern for your backend to reduce API calls:
```javascript
// entitlementCache.js
class EntitlementCache {
constructor(ttlMs = 5 * 60 * 1000) {
// 5 minute default
this.cache = new Map();
this.ttl = ttlMs;
}
get(granteeId) {
const cached = this.cache.get(granteeId);
if (!cached) return null;
const age = Date.now() - cached.timestamp;
if (age > this.ttl) {
this.cache.delete(granteeId);
return null;
}
return cached.entitlements;
}
set(granteeId, entitlements) {
this.cache.set(granteeId, {
entitlements,
timestamp: Date.now()
});
}
invalidate(granteeId) {
this.cache.delete(granteeId);
}
}
export default new EntitlementCache();
```
**Usage in your API/backend:**
```javascript
import cache from './entitlementCache.js';
async function getEntitlements(granteeId) {
const cached = cache.get(granteeId);
if (cached) return cached;
const response = await fetch(`https://api.salable.app/api/entitlements/check?granteeId=${granteeId}`, {
headers: { Authorization: `Bearer ${process.env.SALABLE_SECRET_KEY}` }
});
const data = await response.json();
cache.set(granteeId, data.entitlements);
return data.entitlements;
}
```
**Cache Invalidation via Webhooks:**
```javascript
app.post('/webhooks/salable', async (req, res) => {
const event = req.body;
if (['subscription.created', 'subscription.updated', 'subscription.cancelled'].includes(event.type)) {
const grantees = await getGranteesFromSubscription(event.data.id);
grantees.forEach(id => cache.invalidate(id));
}
res.json({ received: true });
});
```
## Best Practices
- **Choose appropriate TTLs**: 5–15 minutes for most use cases; shorter (2–5 min) for critical features
- **Invalidate on subscription changes**: Use webhook events to clear stale cache entries
- **Handle errors gracefully**: Fail securely by denying access when API calls fail
- **Consider distributed caching**: Use Redis or Valkey for multi-instance deployments
- **Use shorter TTLs for critical features**: Financial operations, admin access, etc.
## Backend/API Caching Considerations
**For single-instance backends**: In-memory caching (as shown above) is sufficient
**For production/multi-instance backends**: Use Redis or Valkey (Redis-compatible) for distributed caching to ensure consistency across instances
## Frontend Caching
For frontend applications, check entitlements through your own backend API endpoints rather than calling Salable directly. Your backend should implement the caching strategy above, and your frontend can use standard HTTP caching or state management libraries (React Query, SWR, etc.) to cache responses from your backend.
**Important: Cache Invalidation Limitations**
Frontend caches are difficult to invalidate because:
- Frontends don't receive webhook events when subscriptions change
- There's no way to notify the client when entitlements are updated server-side
- Frontends must poll or refetch from your backend to get fresh state
**Recommendations for frontend caching:**
- Use shorter TTLs (2–5 minutes) to reduce staleness
- Refetch entitlements after user actions that might change subscriptions (_eg_, after redirecting back from checkout)
- Consider manual refresh options for users ("Refresh subscription status")
- Accept that some delay in reflecting subscription changes is unavoidable on the frontend
Remember: Caching is an optimization strategy to improve performance. Implement what fits your architecture and scale needs.
---
### Webhooks
Source: https://beta.salable.app/docs/webhooks
# Webhooks
## Overview
**[Webhooks](/docs/core-concepts#webhook)** are HTTP callbacks that Salable sends to your application when important events occur. They provide real-time notifications about subscription changes, usage updates, payment events, and more. Instead of constantly polling for changes, your application receives instant notifications.
## Understanding Webhook Destinations
A Webhook Destination is an endpoint on your server that receives event notifications from Salable. Each destination has a URL where events are delivered, a unique signing secret for verifying authenticity, and a selection of event types it listens to.
You can create multiple webhook destinations for different purposes and each one operates independently with its own configuration, delivery history, and retry schedule.
Every webhook destination must listen to at least one event type, but it can listen to any number of the events Salable sends.
## Available Event Types
**Subscription Events** notify you when subscriptions change state:
`subscription.created` fires when a customer successfully completes checkout and a new subscription has been created.
`subscription.updated` occurs whenever subscription details change; plan upgrades or downgrades, quantity adjustments, billing interval changes, or status transitions.
`subscription.cancelled` indicates a subscription has ended, either immediately or at the end of the current billing period.
**Usage Events** track metered billing activity:
`usage.recorded` fires when a usage subscription cycles and the last period's usage has been successfully processed.
`usage.finalised` occurs when a usage subscription has been cancelled or otherwise ends and the last usage record has been processed.
**Payment Events** keep you informed about financial transactions:
`receipt.created` occurs when a one-off line item has been purchased by the user and a receipt has been created.
**Access Control Events** notify you about customer information updates:
`owner.updated` fires when an owner's email address is updated following them successfully purchasing a plan
## Creating Webhook Destinations
Before your application can receive webhook events, you need to create a webhook destination in the Salable dashboard. This process configures where events are sent, which events to send, and generates the signing secret for verification.
### Setting Up Your First Destination
Navigate to **Webhooks** and click the **Create Webhook** button. You'll need to provide two pieces of information: the URL where events will be sent to and which event types this destination should receive.
**Webhook URL Configuration**
Enter the full URL to your webhook endpoint in the URL field (_eg_ `https://yourapp.com/webhooks/salable`). This must be a publicly accessible HTTPS endpoint that can receive POST requests.
Your endpoint should be ready to receive events before creating the destination. While Salable will retry failed deliveries, having your handler ready from the start prevents unnecessary retry cycles.
**Event Type Selection**
Select which event types this destination should receive. You must choose at least one event type, but you can select as many as needed.
### Signing Secret
After creating your webhook destination, the dashboard displays a unique signing secret. This secret is crucial for security as it proves that incoming webhooks are genuinely from Salable and haven't been forged by malicious actors.
Copy the signing secret and store it securely in your environment configuration. You'll need this secret to verify webhook signatures in your application. Never commit signing secrets to version control or expose them in client-side code.
> **Important** Each webhook destination has its own unique signing secret. If you create multiple destinations, you'll need to store each signing secret separately and use the appropriate one when verifying each destination's events.
## Editing Webhook Destinations
Webhook destinations can be modified after creation to update the URL or change which events are delivered.
To edit a destination, find it in the webhooks list and click the **Edit** button. You can update the URL to point to a different endpoint as needed.
You can also modify the event type selection. Adding new event types means the destination will start receiving those events immediately. Removing event types stops delivery of those events going forward however any currently in the process of being sent will still be sent.
> **Note** Editing a webhook destination does not change its signing secret. The same secret remains valid, so you don't need to update your verification code when editing URLs or event types.
## Deleting Webhook Destinations
When you no longer need a webhook destination, you can delete it from the webhooks list. Deleted destinations won't receive new events, and their configuration is deleted from Salable.
## Implementing Webhook Handlers
Your webhook handler is the endpoint that receives and processes events from Salable. A robust handler verifies signatures and responds quickly to avoid timeouts.
### Handler Requirements
Webhook handlers must respond within 15 seconds. If your endpoint takes longer than 15 seconds to return a response, Salable marks the delivery as failed and schedules a retry. This timeout ensures webhook delivery doesn't hang indefinitely.
Your handler should return a 2xx status code (typically 200 or 204) to indicate successful receipt. Any other status code indicates a failure and triggers a retry.
The handler receives a POST request with a JSON payload containing the event data. Reject non-POST requests with a 405 status code. The request will include a couple of important headers: `x-salable-signature` contains the HMAC signature for verification, and `x-salable-timestamp` contains the ISO 8601 timestamp when the request was sent.
### Basic Handler Structure
Here's a basic webhook handler structure in Node.js:
```javascript
import { createHmac, timingSafeEqual } from 'crypto';
export async function handleWebhook(req, res) {
// Only accept POST requests
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const signature = req.headers['x-salable-signature'];
const timestamp = req.headers['x-salable-timestamp'];
const body = req.body;
// Verify timestamp and signature
if (!verifySignature(body, timestamp, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { type, data } = body;
// Process the event based on type
await processEvent(type, data);
return res.status(200).json({ received: true });
} catch (error) {
console.error('Error processing webhook:', error);
return res.status(500).json({ error: 'Processing failed' });
}
}
```
### Signature Verification
Every webhook request includes two important headers: `x-salable-signature` contains the HMAC SHA-256 signature, and `x-salable-timestamp` contains the ISO 8601 timestamp when the request was sent. The signature is computed from a combination of the timestamp and request body using your destination's signing secret.
The verification process involves two security checks. First, verify the timestamp is recent to prevent replay attacks, requests older than 5 minutes should be rejected. Second, compute the expected signature using your signing secret, the timestamp, and the request body, then compare it to the received signature using a constant-time comparison to prevent timing attacks.
```javascript
import { createHmac, timingSafeEqual } from 'crypto';
function verifySignature(body, timestamp, signature) {
const secret = process.env.SALABLE_WEBHOOK_SECRET;
// Check timestamp to prevent replay attacks (5 minute window)
const currentTime = new Date();
const requestTime = new Date(timestamp);
const timeWindow = 5 * 60 * 1000; // 5 minutes in milliseconds
if (Math.abs(currentTime.getTime() - requestTime.getTime()) > timeWindow) {
return false;
}
// Construct the signed payload (timestamp + body)
const rawBody = typeof body === 'string' ? body : JSON.stringify(body);
const payload = `${timestamp}.${rawBody}`;
// Compute expected signature
const expectedSignature = createHmac('sha256', secret).update(payload).digest();
// Constant-time comparison to prevent timing attacks
return timingSafeEqual(expectedSignature, Buffer.from(signature, 'hex'));
}
```
> **Important** Always verify both the timestamp and signature before processing webhook data. The timestamp check prevents replay attacks where old webhook requests are resent maliciously. The signature verification ensures requests genuinely came from Salable and haven't been tampered with.
### Timestamp Validation
The `x-salable-timestamp` header contains an ISO 8601 formatted timestamp indicating when Salable sent the webhook. Validating this timestamp prevents replay attacks where an attacker captures a legitimate webhook request and resends it later.
The standard validation window is 5 minutes. Reject any request with a timestamp older than 5 minutes or in the future by more than 5 minutes. This window accounts for minor clock drift between systems while still protecting against replay attacks.
If your webhook processing includes time-consuming operations that might exceed the 15-second timeout, ensure the timestamp validation happens before those operations. The timestamp check is fast and should always complete within the timeout window.
## Retry Behaviour and Failed Deliveries
Salable automatically retries failed webhook deliveries to ensure your application receives important events even when temporary issues occur. Understanding retry behaviour helps you design handlers that work reliably.
### Automatic Retry Schedule
After a webhook delivery fails, Salable will schedule a retry using exponential backoff.
There will be up to 10 automatic retries per event. After the 10th attempt fails, automatic retries stop. At this point, you can manually resend the event through the dashboard.
### Failed Delivery Handling
Monitor the webhook delivery dashboard to track failed deliveries. Each failure includes the error message and status code returned by your endpoint, helping you diagnose any issues.
When you see failed deliveries, investigate the error message, fix the underlying issue in your handler or infrastructure, and then manually resend the event. The dashboard's resend feature lets you retry individual events once you've confirmed your endpoint is working correctly.
## Resending Events
The webhook dashboard provides tools for manually resending events in three scenarios: pending events, failed events, and successful events.
### Resending Pending Events
Pending events are scheduled for future delivery as part of the retry schedule. If you've fixed an issue with your endpoint and don't want to wait for the scheduled retry, you can resend a pending event to bring it forward to the current time. The event will be sent immediately rather than waiting for the next scheduled retry.
### Resending Failed Events
After the automatic retry limit is exhausted, you can manually resend failed events from the dashboard. Fix the issue that caused the failure, then click the **Resend** button on the failed event. Salable will attempt delivery again immediately.
### Resending Successful Events
You can also resend events that were successfully delivered. This is useful for debugging, testing changes to your webhook handler, or replaying events if you need to rebuild derived data.
When you resend a successful event, it's treated as a brand new delivery attempt. Your handler will receive the event again with the same payload. This is where idempotent processing becomes critical—your handler should recognize it's already processed this event ID and skip reprocessing to avoid duplicate effects.
## Monitoring Webhook Deliveries
The webhook delivery dashboard provides complete visibility into event transmission. For every destination, you can view all sent events, their delivery status, and detailed information about each attempt.
### Delivery History
Each webhook destination has a delivery history showing every event sent to that endpoint. The history includes the event type, timestamp, current status (pending, success, or failed), and a summary of delivery attempts.
Click on any event to view its complete details. This includes the full JSON payload sent to your endpoint, all delivery attempts with their status codes and error messages, request and response headers, and timing information.
### Event Payloads
The dashboard displays the complete payload for each generated event.
### Delivery Attempts
Each event shows the delivery attempts for each destination it was sent to. If an event failed and was retried multiple times, you'll see every attempt with its timestamp, status code, and any error message.
This history helps you understand patterns in failures. If every attempt times out after exactly 15 seconds, you know your handler is too slow. If attempts return 500 errors, there's likely a bug in your handler. If early attempts failed but later ones succeeded, you can correlate success with deployments or infrastructure changes.
## Testing Webhook Handlers
Thorough testing ensures your webhook handler works correctly before real events arrive. Test Mode provides a safe environment for webhook development without affecting production data or processing real payments.
### Test Mode Webhooks
Webhook destinations created in Test Mode only receive events from Test Mode actions. When you complete a test checkout or record test usage, those events go to Test Mode webhook destinations with their associated signing secrets.
This separation ensures test events never reach production handlers and production events never reach test handlers. You can safely experiment with webhook configuration, test signature verification, and trigger failure scenarios without any risk to your production system.
## Troubleshooting
Common webhook issues and their solutions.
### Signature Verification Failures
If signature verification consistently fails, verify you're using the correct signing secret from the webhook destination's detail page. The signed payload must combine the timestamp and body in the format `${timestamp}.${body}`, where the body is the raw JSON string before parsing.
Ensure you're using HMAC SHA-256 for computing signatures and using `timingSafeEqual` for comparison to prevent timing attacks. The signature in the `x-salable-signature` header is hex-encoded, so convert it to a Buffer when comparing with your computed signature.
Common issues include forgetting to include the timestamp in the payload, using the parsed JSON object instead of the raw body string, or comparing strings instead of using constant-time comparison. If timestamps are being rejected, verify your server's clock is synchronized—clock drift beyond the 5-minute validation window will cause rejection.
### Timeout Issues
If deliveries consistently time out, your handler is taking too long to process events. Profile your handler to identify slow operations. Consider moving time-consuming work to background jobs, acknowledge receipt quickly by returning a success response, then process the event asynchronously.
### Events Not Arriving
If you're not receiving expected events, verify the webhook destination is configured to receive that event type. Check that your endpoint URL is correct and publicly accessible.
Review the webhook delivery dashboard to see if events were sent and what responses your endpoint returned. If events were sent but failed, the delivery details show the error. If events weren't sent at all, verify the webhook destination's event type configuration.
## Next Steps
Now that you understand webhooks, explore these related guides:
- **[Testing & Development](/docs/testing-and-development)** - Test webhook handlers in Test Mode
- **[Subscriptions & Billing](/docs/subscriptions-and-billing)** - Understand subscription lifecycle events
- **[Metered Usage](/docs/metered-usage)** - Process usage events and finalization
---
### Model Context Protocol (MCP)
Source: https://beta.salable.app/docs/mcp
# Model Context Protocol (MCP)
## Overview
Salable's **Model Context Protocol** server lets you manage your pricing and billing setup through conversation. Ask an AI assistant to create Products, configure Plans, set up Entitlements, inspect Subscriptions — whatever you'd normally do in the dashboard or via API calls. Your assistant discovers the available tools automatically and figures out how to use them.
## Prerequisites
You'll need a Salable API key, use your secret key:
```bash
export SALABLE_API_KEY="your_api_key_here"
```
Restart your terminal or run `source ~/.zshrc` (or `source ~/.bashrc`) to pick it up.
> **Important** MCP clients read environment variables from your shell, not from `.env` files. `SALABLE_API_KEY` must be exported in your shell before you launch your client. Never commit your API key to version control.
## Setting Up Claude Code
The recommended path in Claude Code is the marketplace plugin:
```bash
claude plugin marketplace add Salable/salable-claude-code-plugin
claude plugin install salable
```
Once installed, type `/salable-monetize` in Claude Code to start the workflow.
### The `/salable-monetize` Workflow
In Claude Code, type `/salable-monetize`, answer a few questions about your pricing model, and the skill handles the rest — from reading your codebase all the way through to generating production-ready code.
Your app needs authentication in place before you run the skill. It generates gating and checkout code that depends on knowing who the current user is. If auth isn't set up yet, the skill will recommend solutions, but the generated code won't work end to end until you've wired it in.
> **Important** Identity fields in Salable (Grantee IDs, Owner IDs) must use non-email identifiers — org IDs, user IDs, or hashed values.
The skill moves through four phases, each building on what came before.
It starts with **discovery** — reading your project to understand your framework, authentication provider, and existing integrations. You don't need to explain your stack; the skill figures it out from your dependencies, config files, and project structure, then maps out where monetization code should live.
Next comes **monetization planning**, where the skill works with you to define which features to gate behind paid **[Plans](/docs/core-concepts#plan)**, what **[Entitlements](/docs/understanding-entitlements)** to create, and how to structure your tiers and pricing. It proposes a model based on common patterns for your type of application, but you're in control — accept the defaults, adjust individual pieces, or redesign from scratch.
Once you've agreed on a model, the skill **provisions your catalog** — creating **[Products](/docs/core-concepts#product)**, Plans, **[Line Items](/docs/products-and-pricing#line-items)**, **[Prices](/docs/products-and-pricing#prices)**, and Entitlements in Salable automatically. All the API calls, ID generation, and relationship wiring happens in the background.
Finally, the skill **generates code** tailored to your framework and auth setup: a pricing page with plan comparisons and checkout buttons, entitlement gating that conditionally renders features, a checkout flow wired to the Salable **[Cart](/docs/cart-and-checkout)** API and Stripe, a **[Subscription](/docs/subscriptions-and-billing)** management page, API routes, and navigation wiring. The code uses your actual Product and Plan IDs and integrates with your existing auth — it isn't generic boilerplate.
The skill supports everything Salable offers — flat-rate, per-seat, metered, and hybrid pricing across multiple cadences and currencies. For more on how these models work, see the **[Products & Pricing](/docs/products-and-pricing)** guide.
### MCP-Only Setup (Without Plugin)
If you only want raw MCP tools (and not the guided plugin workflow), connect Claude Code directly to the Salable MCP server.
The fastest path is downloading the configuration file straight into your project:
```bash
curl -o .mcp.json https://beta.salable.app/mcp.json
```
Claude Code detects the `.mcp.json` file automatically and expands the `${SALABLE_API_KEY}` reference from your shell at startup. Open Claude Code in your project directory and you're connected.
You can also add the server via the CLI:
```bash
claude mcp add --transport http salable https://beta.salable.app/api/mcp \
--header 'Authorization: Bearer ${SALABLE_API_KEY}'
```
By default, this stores configuration in `.mcp.json` at the project root — shared across the team, with each person using their own API key. If you'd prefer the server available across all your projects, add it at **user scope** instead:
```bash
claude mcp add --transport http --scope user salable https://beta.salable.app/api/mcp \
--header 'Authorization: Bearer ${SALABLE_API_KEY}'
```
## Setting Up MCP for Codex
Add the Salable MCP server with a single command:
```bash
codex mcp add salable \
--url https://beta.salable.app/api/mcp \
--bearer-token-env-var SALABLE_API_KEY
```
This stores the server in Codex's user config. If Codex prompts you for an OAuth login, skip it — Salable uses API key authentication only.
## Next Steps
- **[Quick Start](/docs/quick-start)** — Set up your first Product and Subscription
- **[Products & Pricing](/docs/products-and-pricing)** — Pricing configuration and billing models
- **[Core Concepts](/docs/core-concepts)** — Salable's data model and how the pieces fit together
- **[Understanding Entitlements](/docs/understanding-entitlements)** — Feature gating patterns
- **[Subscriptions & Billing](/docs/subscriptions-and-billing)** — Subscription lifecycle management
- [Claude Code Plugin GitHub Repository](https://github.com/Salable/salable-claude-code-plugin) — Source code and issue tracking
## Blog
### Flexible Billing: Beyond Monthly Subscriptions
Source: https://beta.salable.app/blog/beta-features/flexible-billing-intervals
# Flexible Billing: Beyond Monthly Subscriptions
Monthly billing became the SaaS default because it was easy, not because it was optimal. Some customers want annual contracts for budget predictability. Others need weekly billing aligned with their pay cycles. High-velocity products might bill daily. Forcing everyone into monthly subscriptions leaves money on the table: annual prepay improves cash flow, while weekly billing reduces churn in price-sensitive segments. Flexible billing intervals let you meet customers where they are instead of where your billing system allows.
The monthly assumption is embedded so deeply in SaaS thinking that many founders never question it. Pricing pages show monthly rates. Metrics assume monthly cohorts. Churn calculations divide by monthly active users. When a customer asks "can I pay annually?" the answer is often "we'll figure something out," followed by manual invoicing outside your subscription system.
But customer needs genuinely vary. Enterprise buyers often have annual budgeting processes that make monthly payments administratively burdensome. Freelancers and contractors might prefer weekly billing that matches their cash flow. Seasonal businesses want quarterly billing aligned with their busy periods. Products with daily value delivery, like job boards or classified listings, might charge per day rather than per month.
Flexible billing intervals aren't just a customer convenience feature. They're a revenue optimization lever that affects cash flow, churn, and customer lifetime value in measurable ways.
## The Case for Annual Billing
Annual billing deserves special attention because of its impact on unit economics. When a customer pays upfront for a year, several good things happen simultaneously.
Cash flow improves immediately. Instead of waiting twelve months to collect twelve months of revenue, you receive it all at once. This cash can fund growth, reduce the need for financing, or simply provide a buffer against revenue fluctuations.
Churn decreases because the commitment period is longer. A customer who pays monthly has twelve opportunities per year to cancel. A customer who pays annually has one. Even if they become dissatisfied at month three, the sunk cost of their annual payment often keeps them engaged long enough to work through issues. Studies consistently show that annual subscribers have lower churn rates than monthly subscribers, typically by 15-20%.
Customer acquisition costs amortize faster. The marketing spend to acquire a customer is fixed regardless of billing frequency. An annual subscriber who pays \$1,200 upfront delivers more revenue relative to acquisition cost than a monthly subscriber who might churn after four \$100 payments.
The trade-off is discount expectations. Customers paying annually typically expect a discount for their commitment, usually 10-20% compared to monthly rates. This discount is economically rational because the improved unit economics from cash flow and retention more than compensate for the lower total price.
## Implementing Billing Intervals in Salable
Salable supports flexible billing intervals natively, configured at the line item level. Each line item specifies an interval (day, week, month, or year) and an interval count. This combination lets you create standard intervals like monthly or annual, as well as custom intervals like quarterly (every three months) or biannual (every six months).
The interval configuration is straightforward. A monthly subscription sets the interval to "month" and interval count to 1. An annual subscription sets interval to "year" and interval count to 1. A quarterly subscription sets interval to "month" and interval count to 3. A daily subscription sets interval to "day" and interval count to 1.
When you offer multiple billing intervals for the same product, you typically create separate plans or line items for each. A Professional plan might exist in monthly and annual variants, with the annual variant priced at a discount. Customers choose their preferred billing frequency at checkout, and the subscription bills on that schedule going forward.
Renewal and proration calculations adjust automatically for the configured interval. An annual subscription that starts on March 15 renews on March 15 the following year. If the customer upgrades mid-term, proration divides the remaining period appropriately. A customer six months into an annual subscription who upgrades receives credit for half their original payment against the new plan price.
## Weekly and Daily Billing
At the other end of the spectrum from annual contracts, some products benefit from billing cycles shorter than monthly.
Weekly billing suits customers with weekly cash flow patterns. Freelancers and hourly workers often get paid weekly or biweekly. Products targeting these segments reduce friction by aligning billing with income. The subscription renews on Friday; payday is Friday; the charge goes through smoothly.
Weekly billing also reduces the initial commitment barrier. A \$25 weekly subscription feels more accessible than a \$100 monthly subscription, even though the annual cost is higher. For price-sensitive segments, the lower weekly amount may convert better than the equivalent monthly rate.
Daily billing applies to products with highly variable daily value. Job posting sites might charge per day a listing is active. Classified advertising services might bill daily. Cloud infrastructure famously bills by the hour or second. When usage and value are inherently daily, billing should match.
The implementation consideration with short billing intervals is payment processing friction. Each charge incurs processing fees, and very small daily charges may have poor unit economics after fees. Additionally, frequent charges increase the surface area for payment failures. A customer with a weekly subscription experiences payment failure opportunities four times as often as monthly.
For very short intervals, consider separating how you express pricing from how you collect payment. You might advertise a daily rate but charge weekly, or quote a weekly price but bill monthly. Customers see an accessible per-day or per-week figure while you avoid the overhead of frequent small charges.
## Custom Interval Patterns
Standard intervals cover most cases, but some businesses need billing cycles that don't map to days, weeks, months, or years.
Quarterly billing aligns with business planning cycles. Many companies budget and review spending quarterly, making a quarterly subscription a natural fit for their internal processes. Configure this with interval "month" and interval count 3.
Biannual billing (every six months) appears in contracts where annual feels too long but quarterly feels too short. Configure with interval "month" and interval count 6.
Multi-year contracts for enterprise customers might bill annually but commit for two or three years. The billing interval is annual, but the minimum commitment spans multiple billing periods. This requires contract-level configuration beyond just the billing interval.
Academic calendars often don't align with calendar months. A semester-based product might bill for four-month periods that match fall and spring semesters. Configure with interval "month" and interval count 4, with renewal dates set to semester start dates.
## Communicating Billing Options to Customers
When offering multiple billing intervals, clear communication prevents confusion and reduces support requests.
Your pricing page should show options prominently. The conventional UI is a toggle between monthly and annual, showing how prices differ. Expand this pattern if you offer more intervals: a selector showing weekly, monthly, quarterly, and annual with corresponding prices and savings.
Show the math explicitly. "Annual billing saves you \$238 per year" is more compelling than just showing two prices. Customers should understand both what they'll pay and what they'll save by choosing different intervals.
Consider defaulting to annual on the pricing page. If annual billing benefits your unit economics, make it the default selection. Customers who want monthly can switch, but the nudge toward annual captures customers who would accept either.
In checkout, confirm the billing interval before payment. "You'll be charged \$1,188 today and again on [date next year]" eliminates surprises. For monthly, "You'll be charged \$99 today and monthly on the [day]th" sets clear expectations.
## Changing Billing Intervals
Customers sometimes want to switch billing intervals mid-subscription. A monthly subscriber might want to convert to annual to lock in a rate or capture a discount. An annual subscriber might want to switch to monthly due to budget constraints.
These conversions require careful handling of timing and money. Converting from monthly to annual typically means charging for a full year minus any remaining monthly credit. Converting from annual to monthly might mean prorating the unused annual term into monthly credits.
Salable handles interval changes through subscription modification. When a customer changes their interval, the system calculates appropriate credits and charges, generates the necessary invoice adjustments, and updates the renewal schedule. Your application receives webhooks for the change and any associated billing events.
Set policies for interval changes and communicate them clearly. Can customers switch from annual to monthly, or only monthly to annual? Is switching mid-term allowed, or only at renewal? What happens to discounts if a customer downgrades from annual? These policies should be documented and consistently applied.
## Billing Intervals and Revenue Recognition
Different billing intervals affect when you recognise revenue under accrual accounting. An annual subscription paid upfront represents twelve months of unearned revenue that you recognise monthly as you deliver service.
This distinction matters for financial reporting and taxes. Cash basis accounting shows annual payments as revenue when received. Accrual basis accounting spreads that revenue over the subscription period. If you're operating under GAAP or IFRS standards, your auditors will want to see proper deferral of prepaid subscriptions.
Salable's reporting supports both views. You can see cash collected by period and revenue recognised by period. The deferred revenue balance shows how much you've collected but not yet earned. This data feeds into your financial systems for accurate reporting.
## The Flexibility Advantage
Every billing interval decision trades off between customer preferences, unit economics, and operational complexity. The optimal mix depends on your specific business, but having options lets you experiment and optimise.
Start with the intervals that obviously serve your market. If you're selling to enterprises, annual billing is likely essential. If you're targeting freelancers, consider weekly. If you're purely consumption-based, daily might make sense.
Add intervals based on customer demand. If multiple customers ask for quarterly billing, the request is probably worth serving. If no one asks for weekly, don't build it just for theoretical flexibility.
Monitor the impact of different intervals on your key metrics. Do annual subscribers retain better? Do weekly subscribers convert more easily? Let data guide which intervals you promote versus which you merely make available.
The monthly default persists because it's familiar, not because it's universal. Your customers have diverse needs, cash flows, and planning cycles. Flexible billing intervals let you serve them all without fragmenting your product or complicating your operations. The subscription bills when it makes sense for the customer, and your business captures the value either way.
---
### Why Your Pricing Page Is Losing You Customers
Source: https://beta.salable.app/blog/insights/pricing-page-losing-customers
# Why Your Pricing Page Is Losing You Customers
Pricing pages are high-stakes real estate. Visitors who reach your pricing page have significantly higher conversion intent than average—they're actively evaluating whether to buy. Yet many pricing pages squander this opportunity. The typical SaaS pricing page commits the same sins: feature comparison tables that read like spec sheets, tier names that mean nothing, and a sea of checkmarks that blur together. Customers land on pricing pages with a question: "Is this right for me?" Feature matrices don't answer that question. They answer "What do I get?"—which is a different, less important inquiry. Fixing pricing pages requires shifting from features to outcomes, from comparison to recommendation.
This shift isn't cosmetic. It reflects a fundamental change in how you think about the job your pricing page needs to do. Most pricing pages are designed as information repositories; they should be designed as decision-making tools.
## The Feature Matrix Trap
Visit any SaaS pricing page and you'll likely find a comparison table dominating the layout. Three or four columns representing tiers, with rows of features stretching down the page. Checkmarks and crosses indicate what's included at each level. The logic seems sound: give customers all the information they need to make an informed decision.
Comparison tables aren't the problem. The problem is how most tables are designed: optimising for comprehensiveness, not comprehension. A table with forty features tells customers everything and nothing simultaneously. They can see that the Pro tier includes "Advanced Analytics" while Basic doesn't, but they don't know whether Advanced Analytics matters for their use case. They see that Enterprise includes "Custom Integrations" but have no framework for evaluating whether they'll ever need custom integrations.
The format assumes customers arrive with perfect knowledge of what they need. In reality, most visitors are still figuring out whether your product solves their problem at all. They're not ready to parse the differences between "10 projects" and "Unlimited projects"—they're wondering if this is the right tool for someone in their situation.
This mismatch explains why bounce rates on pricing pages are often surprisingly high. Customers come seeking clarity and find complexity. They wanted guidance and got a spec sheet.
## What Customers Actually Want to Know
When someone visits your pricing page, they're in one of three mental states. Some are just browsing, getting a sense of what you charge to decide if you're even worth evaluating. Others are seriously considering your product and need to determine which tier fits. A few are ready to buy and just need to confirm the details before committing.
For all three groups, the central question is the same: "Is this right for someone like me?" Notice that this isn't a question about features. It's a question about fit.
Features matter eventually, but only in service of fit. A customer doesn't care about "Advanced Analytics" in the abstract—they care whether it will help them accomplish what they're trying to accomplish. The product manager evaluating your tool wants to know if it will make their team's weekly planning easier. The startup founder wants to know if it can scale with their growth. The enterprise buyer wants to know if it meets their security requirements.
Effective pricing pages answer the fit question first and the feature question second. They help customers see themselves in one of the tiers before diving into what that tier includes. "For growing teams shipping weekly" tells customers more at a glance than "50 projects, 10 integrations, advanced reporting."
## Tier Names That Mean Nothing
Among the most common pricing page failures is tier naming that provides zero information. Basic, Pro, Enterprise. These names persist because they're familiar, not because they're useful.
Consider what these names actually communicate. "Basic" says "this is our stripped-down version"—hardly an aspirational choice. "Pro" suggests this is for professionals, but which professionals? The freelance designer and the Fortune 500 IT department are both professionals. "Enterprise" has become shorthand for "expensive and complicated," which may not be the message you want to send.
Compare this to tier names that describe who the tier is for. "For Individuals" immediately tells solo users where to look. "For Teams" signals collaboration features. "For Organizations" suggests scale and administration capabilities. These aren't clever, but they're clear. Customers can self-select without decoding marketing jargon.
The test for tier naming is simple: can a customer new to your product look at the names alone and make a reasonable guess about which tier fits them? If your names require reading the fine print to differentiate, they're not doing their job.
## The Checkmark Problem
Checkmarks have become the default visual language for feature comparison, but they often create more confusion than clarity. A page full of checkmarks becomes visual noise—your eye can't distinguish what's important from what's filler. Everything looks equally weighted, even when features vary enormously in significance.
Worse, checkmarks encourage a style of comparison that serves nobody. Customers start counting checkmarks as if more is always better, even when many of those features are irrelevant to them. They might choose a higher tier because it has more checkmarks, paying for capabilities they'll never use. Or they might choose a lower tier because it seems "enough" based on checkmark count, missing a critical feature buried in the comparison.
The alternative isn't eliminating feature comparison entirely—sometimes customers genuinely need to compare specific capabilities. But lead with outcomes and value first, then make detailed feature comparison available for those who want to dig deeper. A "Compare all features" link below your primary pricing presentation lets detail-oriented customers find what they need without overwhelming everyone else.
## Anchoring and the Architecture of Choice
Pricing page design isn't just about information—it's about psychology. How you present options influences which options customers choose. This is the realm of choice architecture, and most pricing pages get it wrong.
The most common mistake is presenting tiers as equals and letting customers figure out the right choice. Three cards of identical size, equal visual weight, no recommendation. This feels neutral but actually creates friction. Customers have to do all the work of deciding, with no guidance from you.
Contrast this with pricing pages that recommend a tier. Highlighting one option as "Most Popular" or "Best Value" reduces decision fatigue and anchors customers to a specific choice. They can still pick a different tier, but they start from a recommendation rather than a blank slate. Highlighted tiers convert better—not because customers blindly follow suggestions, but because recommendations provide useful information about what most people need.
Anchor pricing also shapes perception through contrast. When you present a $29 tier next to a $149 tier, the $29 option feels affordable by comparison. The $149 tier might only convert a small percentage of visitors, but its presence makes the middle tier look reasonable. This isn't manipulation; it's recognition that humans evaluate prices relative to available alternatives, not in absolute terms.
The order of tiers matters too. Research from CXL found users showed preference for higher-priced options when expensive tiers were presented first—though lab preferences don't always match real purchasing behaviour. When customers see $299/month first, $99/month feels like a deal. When they see $29/month first, $99/month feels like a big step up. Neither ordering is inherently better—the right choice depends on whether you want to anchor high or lead with accessibility.
## Outcome-First Pricing Page Design
Fixing a feature-focused pricing page isn't complicated, but it requires rethinking what the page is for. Instead of answering "What do I get?" first and hoping customers figure out fit, answer "Is this right for me?" first and let features support that answer.
Start each tier with a clear statement of who it's for or what outcome it delivers. Not "10 users included" but "For small teams collaborating on projects." Not "Advanced security" but "For organisations with compliance requirements." These outcome statements help customers see themselves in the tier before evaluating features.
Below the outcome statement, include a brief value proposition—what makes this tier worth the price. "Everything you need to ship your first project" works better than a feature list because it speaks to the customer's goal, not the product's capabilities.
Features should support the outcome and value proposition, not replace them. Include the key differentiators that matter for the target segment, but resist the urge to list everything. Three to five bullet points are usually enough. Save the comprehensive feature list for a secondary comparison view.
Social proof reinforces fit. Testimonials from customers in the target segment ("Perfect for our three-person design team") help visitors see themselves in your existing customer base. The specific customer type matters more than generic praise.
## The Call-to-Action Gap
Even pricing pages that get the content right often stumble at the final step: the call to action. Vague CTAs like "Get Started" or "Learn More" create uncertainty about what happens next. Customers wonder: Will I be charged immediately? Do I need to talk to sales? Is there a free trial?
Effective CTAs are specific about the next step. "Start your 14-day free trial" tells customers they can try before buying. "Talk to sales" sets expectations for enterprise tiers that require custom pricing. "Buy now" is appropriate when customers can complete the purchase immediately.
The CTA should also resolve potential objections. "No credit card required" eliminates a common friction point for free trials. "Cancel anytime" addresses fear of commitment. "See a demo first" offers an alternative for customers not ready to commit.
Different tiers often warrant different CTAs. Self-serve tiers might lead directly to signup, while enterprise tiers route to a sales conversation. Forcing all customers through the same funnel ignores that they're at different stages of readiness.
## Testing Beyond the Obvious
Most pricing page optimization focuses on the obvious: button colors, tier names, feature lists. But the highest-impact changes often come from testing structural assumptions.
Test whether customers actually want to see all features, or whether a simplified presentation converts better. Test whether recommended tiers help or feel pushy for your specific audience. Test whether annual pricing prominently displayed increases commitment or creates sticker shock.
Consider testing entirely different page structures. Does a single landing page for all tiers work better than separate pages per tier? Does a calculator that recommends tiers based on inputs outperform static comparison? Does embedding pricing in the main navigation versus hiding it behind "Contact" affect perception?
The meta-lesson: best practices are starting points, not destinations. Your pricing page exists in the context of your specific product, audience, and market. What works for another SaaS might not work for you.
## From Comparison to Recommendation
Your pricing page's job isn't to provide information—it's to help customers make a confident decision. Feature matrices provide information but don't guide decisions. They leave customers to figure out fit on their own, armed with details that may or may not matter to their situation.
The shift from features to outcomes transforms your pricing page from a spec sheet into a recommendation engine. Lead with who each tier is for. Frame value in terms of customer outcomes. Use features to support the value proposition, not replace it. Recommend a tier rather than presenting options as equals.
People don't want to compare features. They want to know which option is right for someone like them. "For teams shipping weekly" tells them more than "Unlimited deployments" ever could. Answer the fit question first, and the feature question answers itself.
Your pricing page is too important to be a passive repository of information. Make it an active guide that helps visitors see themselves as customers.
---
### Webhooks: Real-Time Billing Events for Your Application
Source: https://beta.salable.app/blog/beta-features/webhooks-real-time-billing-events
# Webhooks: Real-Time Billing Events for Your Application
Your customer just upgraded their plan, but your application still shows them the old features. Maybe your cron job will catch it in an hour. Maybe tomorrow. Polling for subscription changes works until it doesn't, and when it fails, customers notice. Webhooks flip the model. Instead of asking "has anything changed?" every few minutes, your application receives a notification the moment a change occurs. Subscription created, payment failed, usage recorded: your system knows immediately. The customer upgrades at 2:47 PM, and by 2:47 PM their new features are live.
The polling approach seemed reasonable when you built it. Every fifteen minutes, your background job queries the billing API for subscriptions modified since the last check. It processes any changes, updates your local database, and goes back to sleep. The simplicity was appealing, and for low volumes it worked fine.
Then you scaled. The fifteen-minute window started feeling long when customers complained about delayed access. You shortened it to five minutes, then one minute. Now your job runs constantly, making API calls that usually return empty results. Your rate limits are stressed. Your billing API costs increased. And the fundamental problem remained: customers still waited up to a minute for changes to propagate.
Webhooks eliminate this latency entirely. The billing system pushes events to your application as they occur. The upgrade happens, the event fires, your handler runs, and the features unlock. The delay drops from minutes to milliseconds.
## The Event-Driven Model
Understanding webhooks requires shifting from pull to push thinking. In the polling model, your application is the active party, asking the billing system for updates on a schedule you control. In the webhook model, the billing system is the active party, pushing updates to an endpoint you've registered.
This inversion has profound implications for how you architect your application. Polling code typically lives in a scheduled job that reads data and updates state. Webhook code lives in an HTTP handler that receives data and updates state. The logic is similar, but the trigger is different.
Webhooks arrive as HTTP POST requests to an endpoint you specify. The request body contains a JSON payload describing what happened: the event type, the relevant objects (subscription, invoice, customer), and timestamps. Your handler parses the payload, validates that it's legitimate, and processes the event.
The key mental model is that webhooks are facts about things that happened. "Subscription SUB-123 was upgraded to Professional plan at 14:47:23 UTC." Your handler's job is to update your system's state to reflect this fact. The subscription in your database should now show Professional plan. The customer's entitlements should reflect Professional features.
## Event Types That Matter
Salable sends webhooks for all significant billing events. Knowing which events to handle lets you build responsive applications that stay synchronised with billing state.
**Subscription lifecycle events** tell you when subscriptions are created, updated, or cancelled. A new subscription means a new customer to provision. An updated subscription might mean changed entitlements or seat counts. A cancelled subscription means access should end at the appropriate time.
**Payment events** inform you about billing success and failure. A successful payment confirms continued access. A failed payment might trigger a grace period or dunning flow. A dispute or refund might require special handling depending on your policies.
**Invoice events** track the billing document lifecycle. An invoice created event lets you add custom line items before finalization. An invoice paid event confirms payment processing. An invoice past due event might trigger account restrictions.
**Usage events** for metered billing confirm that usage records were received and will be included in the next invoice. These events provide an audit trail and let you verify that your usage reporting is being processed correctly.
Each event type has a defined payload structure documented in Salable's webhook reference. The structure includes the event type identifier, a timestamp, and the relevant objects with their current state.
## Building a Robust Webhook Handler
A production webhook handler needs more than a simple HTTP endpoint. Several concerns require careful implementation to ensure reliability.
**Signature verification** ensures that webhooks actually came from Salable and weren't forged by attackers. Each webhook includes a signature header computed from the payload and a secret key. Your handler must verify this signature before processing. Salable's SDKs include signature verification functions, so you don't need to implement the cryptography yourself.
```javascript
const isValid = salable.webhooks.verify(payload, signatureHeader, webhookSecret);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
```
**Idempotent processing** handles the case where the same event arrives multiple times. Network issues, retries, and edge cases can cause duplicate delivery. Your handler should process each event exactly once, even if it's received multiple times. Typically this means checking whether you've already processed an event ID before taking action.
**Response timing** matters because webhook delivery systems expect quick responses. Your handler should acknowledge receipt by returning a 200 status code promptly, ideally within a few seconds. If your processing takes longer, acknowledge first and process asynchronously. A handler that times out will cause retries and potential duplicate processing.
**Error handling** determines what happens when processing fails. Return a 5xx status code to signal that Salable should retry the webhook. Return a 4xx status code if the webhook itself is malformed and retries won't help. Log errors with enough context to debug later.
## Handling Common Event Patterns
Certain event sequences appear repeatedly across applications. Understanding these patterns helps you implement correct handling.
**Subscription creation** typically triggers user provisioning. When you receive a `subscription.created` event, you might create a workspace for the customer, initialize their settings, send a welcome email, and update your customer database. The order of these operations might matter; ensure the workspace exists before sending an email with a link to it.
**Subscription upgrade** means entitlement changes. The customer's old features should continue working while new features become available. Check the `previousAttributes` field in the event payload to see what changed, and update your entitlement cache accordingly.
**Payment failure** starts a grace period in most applications. The customer's access continues temporarily while you attempt to collect payment. Send notifications encouraging them to update their payment method. After a configured number of retries or days, a follow-up event indicates whether payment succeeded or the subscription should be suspended.
**Subscription cancellation** has immediate and scheduled variants. An immediate cancellation means access ends now. A scheduled cancellation (at period end) means access continues until the paid period expires. Your handler should check the cancellation timing and act appropriately.
## Testing Webhook Handlers
Webhook handlers are notoriously difficult to test because they receive external events that are hard to simulate. Several strategies make testing tractable.
**Local testing** with webhook forwarding lets you receive real webhooks on your development machine. Tools like ngrok or Cloudflare Tunnel expose your localhost to the internet, giving you a public URL to configure as your webhook endpoint. This lets you trigger real events and see how your handler responds without deploying to a server.
**Payload capture** during local development gives you test fixtures. Log the raw payloads your handler receives, then save representative examples as JSON files. Replay these in unit tests to verify your parsing, validation, and processing logic without network dependencies.
**Integration tests** in staging environments verify the full flow. Create a test subscription, observe the webhook arrive, verify your handler processed it correctly. Automate these tests to run on deployment.
**Monitoring in production** catches issues that testing misses. Track webhook reception rates, processing times, and error rates. Alert on anomalies like sudden drops in events or spikes in errors. The sooner you notice a problem, the sooner you can fix it.
## Webhook Reliability and Retries
Network failures happen. Servers go down. Bugs cause handlers to crash. A robust webhook system handles these failures gracefully.
Salable's webhook delivery includes automatic retries with exponential backoff. If your handler returns an error or times out, the webhook is retried after a delay. The delay increases with each retry, preventing retry storms from overwhelming a struggling server.
The retry schedule follows a pattern: immediate retry, then one minute, five minutes, thirty minutes, two hours, and so on up to a configurable maximum. Most transient failures resolve within the first few retries. Persistent failures eventually stop retrying, and the event moves to a dead letter queue for manual investigation.
Your handler should be idempotent to handle retries gracefully. If the first attempt partially succeeded before failing, the retry shouldn't double-count the action. Check whether the event was already processed, or design your operations to be naturally idempotent (setting state rather than incrementing counters).
## Monitoring and Debugging
Webhook-driven architectures require visibility into the event flow. When something goes wrong, you need to understand what events arrived, how they were processed, and what state resulted.
**Logging every event** provides an audit trail. Log the event type, relevant IDs, timestamp, and processing outcome. When investigating an issue, you can trace from a customer complaint to the events that should have fired to how your handler processed them.
**Monitoring delivery health** catches systemic issues. Track the volume of webhooks received over time. A sudden drop might indicate a configuration problem or Salable infrastructure issue. A sudden spike might indicate unusual activity worth investigating.
**Dashboard visibility** through Salable's interface shows recent webhooks, their delivery status, and response codes from your handler. When debugging, you can see exactly what was sent and whether your endpoint acknowledged receipt.
**Alert on failures** to catch problems before customers report them. If your handler starts returning errors, you want to know immediately. Configure alerts for error rate thresholds so you can investigate and fix issues proactively.
## Moving Beyond Polling
Migrating from polling to webhooks typically happens incrementally. You add webhook handlers alongside existing polling, verify they work correctly, then remove the polling once you trust the new system.
During the transition, reconciliation jobs help catch discrepancies. Run a periodic job that compares your local state against the billing API. Any differences indicate either a missed webhook or a handler bug. As your webhook handling matures, these reconciliation runs should find fewer issues until you're confident enough to remove them.
The operational benefits of webhooks compound over time. Lower API usage reduces costs. Faster propagation improves customer experience. Event-driven architecture enables real-time features that polling can't support. The initial investment in building robust handlers pays dividends in everything built on top of them.
## Real-Time Applications
Webhooks enable application patterns that aren't possible with polling.
**Instant feature unlocks** let customers use new features the moment they pay. No more "your access will be updated within the hour" messages. The upgrade event fires, your handler runs, and the feature is live.
**Payment failure responses** can be immediate and contextual. Instead of a batch email hours later, you can show an in-app notification the moment payment fails. "We couldn't charge your card, click here to update" appears while the customer is actively using your product.
**Usage dashboards** can update in real-time as metered events are recorded. The customer watches their usage count increment with each API call, building confidence that billing will be accurate.
**Automated workflows** trigger on billing events without delay. A new subscription could kick off an onboarding sequence. A cancellation could trigger a feedback survey. A payment failure could assign a task to your success team. The speed of webhooks makes these workflows feel automatic rather than batched.
Your billing system becomes a real-time data source rather than a database you periodically sync. Events flow through your application as they happen, keeping every system aligned without manual intervention or scheduled jobs. The customer upgraded at 2:47 PM, and by 2:47 PM everything in your application reflects that upgrade. That's the power of webhooks.
---
### The Cart System: Core Products with Add-Ons Done Right
Source: https://beta.salable.app/blog/beta-features/cart-system-addons
# The Cart System: Core Products with Add-Ons Done Right
Your customer wants your Professional plan plus the Analytics add-on, the API Access module, and maybe the White Label extension. Traditional billing systems force a choice: separate subscriptions that fragment the customer relationship, or a monolithic plan that bundles everything whether customers want it or not. Neither works. Salable's cart system solves this by letting customers purchase multiple plans as a single subscription. The core product and add-ons live together, billed together, managed together. And when needs change, customers can add new capabilities, remove what they don't use, or replace one add-on with another, all without creating subscription chaos.
The problem with separate subscriptions goes beyond administrative inconvenience. Each subscription is an independent relationship with its own lifecycle. Different renewal dates mean staggered billing that confuses customers. Cancelling the core product doesn't automatically cancel add-ons, leading to surprised complaints when charges continue. Cross-product entitlements become complicated when the systems aren't connected. You end up building integration logic between your own subscriptions.
Monolithic bundles avoid these problems but create new ones. If the Analytics add-on and the API Access module are bundled into your Professional plan, customers who want only one must pay for both. Those who need neither but want the core Professional features are subsidising capabilities they don't use. Unbundling means creating more plans, which means a more complex pricing page and harder purchasing decisions for customers.
The cart model lets you bundle without bundling. Products and add-ons remain separate for pricing and entitlement purposes, but they combine into a single subscription for billing and lifecycle management. Customers build their own bundle from your menu of options.
## How Carts Create Composable Subscriptions
A cart in Salable works like a shopping cart in e-commerce, but for subscription products. Customers add plans to their cart, and when they check out, all the plans become line items in a single subscription.
The subscription that results has a unified billing cycle. All line items renew together. The customer sees one charge on their credit card statement, not separate charges for each component. If they view their subscription in your customer portal, they see everything they've purchased as parts of a whole.
Entitlements from all the plans in the subscription apply to the customer. If the Professional plan includes project management features and the Analytics add-on includes dashboard features, the customer has access to both. Your entitlement checks don't need to know about the cart structure; they just check whether the customer's subscription includes the relevant entitlement.
This composition is flexible at purchase time but coherent after purchase. The customer chooses what they want; once subscribed, it behaves as a single unit.
## Designing for Cart-Based Purchasing
Thinking in terms of carts changes how you structure your pricing. Instead of comprehensive plans that try to serve all customers, you design a core product and a catalogue of add-ons.
The core product should provide standalone value. It's what most customers need and what defines your product's primary use case. Everything else becomes optional add-ons that extend, enhance, or specialise the core functionality.
Add-ons typically fall into a few categories. Feature extensions add capabilities beyond the core product, like advanced analytics, API access, or white-label options. Capacity extensions increase limits, like additional storage, more users, or higher API rate limits. Professional services like priority support, dedicated account management, or custom training can also be add-ons rather than bundled into premium tiers.
The key principle is that add-ons should provide independent value. A customer who buys the Analytics add-on should receive clear benefit from it regardless of whether they also buy the API Access module. If two add-ons only make sense together, consider combining them into a single add-on or making one dependent on the other.
## The Checkout Experience
Cart-based checkout needs UI that supports browsing and selection. Unlike single-product checkout where the customer clicks a plan and enters payment details, cart checkout involves building a bundle before payment.
Your pricing page becomes more of a product catalogue. Each option shows what it provides and what it costs. Customers can select their core plan and then browse available add-ons, toggling them into their cart as they go.
The cart summary should be visible throughout the selection process. Customers need to see their running total and the list of what they've selected. Changes to the cart update the summary immediately, so there are no surprises at checkout.
For simple product catalogues, an accordion or toggle interface works well. Core plans show prominently, add-ons appear as checkboxes or toggles beneath them, and the selected items stack in a sidebar cart. For more complex catalogues, a dedicated cart page where customers review and adjust their selections before proceeding to payment might be clearer.
The checkout itself collects payment information and processes the composed cart as a single subscription. Salable handles the cart-to-subscription conversion, creating the unified subscription with all selected line items. The customer's payment method is charged for the combined total.
## Modifying Subscriptions After Purchase
One advantage of the cart model is that subscriptions can be modified without wholesale replacement. Customers can add new add-ons to an existing subscription, remove add-ons they no longer need, or replace one add-on with another.
Adding an add-on to an existing subscription happens immediately—no cart required. The customer selects the new add-on, and the subscription updates to include it. Their payment method is already on file, so billing prorates automatically, charging for the remainder of the current billing period without requiring another checkout flow.
Removing an add-on follows a similar flow but in reverse. The customer indicates they want to remove something, and the subscription adjusts. Depending on your policies, the removal might be immediate or take effect at the next renewal. Credits for unused time can apply or not based on how you've configured the add-on.
Replacing one add-on with another, such as swapping a standard support add-on for a premium support add-on, combines removal and addition in a single operation. The customer sees this as an upgrade or change rather than two separate transactions.
## Managing Complex Bundles
Some products have interdependencies between add-ons. The API Access module might require the Professional plan or above. The White Label extension might be incompatible with certain integrations. Your checkout flow needs to handle these constraints.
Implement availability logic in your pricing page and checkout UI. When a customer selects a core plan, show only the add-ons compatible with that plan. Hide or disable options that don't apply, with explanations where helpful. This filtering happens in your application before the cart reaches Salable, ensuring customers only see valid combinations.
For complex dependency trees, document the relationships clearly for customers. A diagram showing which add-ons work with which plans, or a guided selection flow that filters options based on prior selections, helps customers navigate without frustration.
## Billing and Revenue Recognition
Cart-based subscriptions consolidate billing but still provide line-item visibility for accounting and analysis.
Invoices show each plan in the subscription as a separate line item. The Professional plan is \$99, the Analytics add-on is \$29, the API Access module is \$49; the total charge is \$177. This breakdown helps customers understand their charges and simplifies expense allocation for businesses that track costs by category.
For your revenue reporting, you can see performance at the line item level. Which add-ons are most popular? What's the average add-on revenue per subscription? Which combinations drive the highest lifetime value? This granularity helps you optimise your product catalogue and pricing.
Revenue recognition treats the subscription as a unit but can segment by line item if needed. The total monthly recurring revenue from a cart-purchased subscription attributes appropriately across the components that comprise it.
## The Strategic Advantage
Cart-based purchasing isn't just operational convenience; it's a strategic advantage in how you monetise and grow.
Modularity lets you serve more customer segments without proliferating plans. Instead of Starter, Professional, Professional Plus, Professional Plus with Analytics, and Professional Enterprise bundles, you have a core product and a menu. Customers build what they need; you don't need to anticipate every combination.
Expansion revenue becomes natural. When a customer needs more capability, they add an add-on. This is lower friction than upgrading to a higher tier plan that includes capabilities they didn't ask for. The upsell conversation is "would you like this specific thing?" rather than "would you like to pay more for a bigger package?"
Churn reduction comes from precise fit. Customers who have exactly what they need are less likely to leave than customers paying for capabilities they don't use. The subscription matches their requirements rather than approximating them.
Testing new products becomes simpler. Launch a new add-on, make it available in the cart, see who buys it. If it succeeds, keep it. If it doesn't, remove it. The cart architecture supports experimentation without restructuring your core offering.
## Building for Carts
Implementing cart-based purchasing requires changes across your pricing page, checkout flow, customer portal, and entitlement logic. The scope is significant but the components are well-defined.
Your pricing page needs to support selection and cart building. This is primarily a frontend concern: displaying options, tracking selections, showing the running total, and passing the cart to checkout.
Your checkout flow needs to accept a cart and create a composed subscription. Salable's checkout handles this, receiving the cart items and processing them into a single subscription. You pass the cart contents; the system handles the rest.
Your customer portal needs to show the subscription composition and support modifications. Customers should see what's in their subscription, what add-ons are available, and have clear paths to add or remove items.
Your entitlement logic doesn't need to change if it's already checking subscription entitlements. The entitlements from all cart items combine in the subscription, so standard entitlement checks work without modification.
The cart system transforms your billing from a constraint into an enabler. Customers get exactly what they need. You capture revenue from every capability they value. And the subscription relationship remains unified, simple to manage, easy to understand, and straightforward to modify as needs evolve.
Your Professional plan plus Analytics plus API Access plus White Label becomes one subscription, one invoice, one relationship. That's what add-ons done right looks like.
---
### Why Letting Customers Pay Less Makes More Money
Source: https://beta.salable.app/blog/insights/letting-customers-pay-less
# Why Letting Customers Pay Less Makes More Money
Every customer extracts different value from your product. Usage patterns, team sizes, and priorities vary enormously—and a handful of predefined tiers can only approximate that reality. The approximation works for most customers most of the time, but when it doesn't, the mismatch pushes them toward cancellation. Customers who'd happily keep paying are forced out if you're unwilling to compromise and offer them a middle ground.
## The Binary Choice
Tiers bundle features together, and that bundling creates friction the moment a customer's needs don't align with what's on offer. A customer who relies on three features from your Starter plan and one from Professional is stuck. The full tier jump costs more than the single feature is worth to them, so they go without it—or worse, they start wondering whether a competitor offers a better fit. Customers who do make the leap to the higher tier are reminded they're overpaying for features they'll never use every time they receive an invoice.
Both situations push toward cancellation, and the underlying cause is the same. Rigid tiers turn pricing into an all-or-nothing proposition. The customer would happily pay _something_ in between, but "in between" doesn't exist on your pricing page. So they leave.
## The Cost of Leaving
Losing a customer costs more than merely the loss in revenue. Acquiring a new customer costs [somewhere between five and twenty-five times more](https://hbr.org/2014/10/the-value-of-keeping-the-right-customers) than keeping an existing one, with [SaaS acquisition costs ranging from a few hundred to several thousand dollars per customer](https://firstpagesage.com/reports/b2b-saas-customer-acquisition-cost-2024-report/) depending on your industry and segment. Every cancellation writes off that investment entirely.
Run the numbers on a specific scenario. A customer on your \$149/month Professional plan isn't using half the features in their tier. They cancel. That's \$1,788 in annual revenue gone, plus the full acquisition cost sunk. Dropping them to your \$49/month Starter tier might not work either—if the features they do use aren't on that plan, there's nowhere to step down. But a tailored plan at \$79/month keeps \$948 coming in—revenue that continues paying back your acquisition investment rather than evaporating.
And beyond the revenue, you lose the relationship. Once a customer has cancelled, they're back on the open market evaluating alternatives. Winning them back means re-engagement campaigns and win-back offers that eat into your margins—assuming they come back at all. A customer who stays, even at a lower price, keeps your product embedded in their workflow.
## Tailored Plans
You could add a fourth or fifth tier, and it might fit some customers better—but there will always be edge cases. More plans also means a more confusing product, making checkout decisions harder and leading to failed conversions.
The solution is tailored plans—arrangements built around what a customer actually uses. Instead of rigid bundles, you treat each feature as a granular entitlement. Your standard tiers are just predefined combinations of those entitlements, and you can recombine them to accommodate whatever deal you need to make.
Someone overpaying for features they don't need gets a plan with entitlements trimmed to their actual usage at a lower price. Someone on a lower tier who needs a single entitlement from the tier above gets exactly that, without paying for everything else bundled alongside it.
The result is a plan that accurately reflects the value the customer receives. Less revenue than a full tier upgrade, more revenue than a cancellation. Your standard tiers stay clean for self-service customers while tailored arrangements happen behind the scenes for those who don't fit the mould.
## The Maths
A single month's invoice tells you almost nothing about a customer's value. Revenue across the entire relationship is what matters.
A customer on a tailored plan at \$79/month for three years contributes \$2,844. A customer who pays \$149/month for six months before cancelling contributes \$894. The monthly revenue is lower, but the lifetime revenue is often higher. And the Starter customer on \$49/month who adds a single feature at \$30/month generates \$360/year you'd never capture if the only option was a full tier jump to \$149.
Multiply that across your customer base. If ten percent of your customers would cancel over a pricing mismatch, and you retain even half of them through flexible arrangements, the numbers compound quickly. Each retained customer is revenue you keep without spending a penny on acquisition. Each one is a customer who stays in your product, benefits from every improvement you ship, and grows with you instead of evaluating your competitors.
Rigid tiers optimise for simplicity at the point of sale. Flexible pricing optimises for the months and years that follow.
## The Upgrade Potential
Retained customers on tailored plans are future revenue without further acquisition costs. You've already done the hard work of getting them into your product. They know how it works, their data is there, and switching to a competitor means starting over. Every one of them costs less to keep than to win back or replace.
A tailored plan at a lower price isn't a ceiling—it's a starting point. There's a gap between what the customer pays now and what they could pay as they grow, and that gap is your upside. As their team expands or their needs change, each new requirement is an opportunity to sell them more entitlements.
## Tailored Plans, Built into Salable
Salable supports this out of the box. Entitlements are a core primitive — every feature in your product can be packaged, combined, and recombined into whatever plan a customer needs. Create your standard tiers for self-service, then build tailored plans for the customers who don't fit. No custom billing logic, no workarounds. The flexibility is built into the platform.
Customers who can pay less tend to stay longer, and customers who stay longer generate more revenue over time.
---
### Grantee Groups: Team Subscriptions Done Right
Source: https://beta.salable.app/blog/beta-features/grantee-groups-team-subscriptions
# Grantee Groups: Team Subscriptions Done Right
Per-seat pricing sounds simple until you implement it. Who pays and who uses aren't the same question. A company administrator buys 50 seats, but the individual team members need access. Adding someone to the subscription shouldn't require the billing owner to click buttons in your UI. And what happens when someone leaves the team? Grantee groups model these relationships explicitly. The owner holds the billing relationship, the grantees receive access, and the group manages membership. Changes propagate automatically, seat limits enforce themselves, and your code doesn't need to track who paid for whom.
The complexity sneaks up on you. Early customers are individuals who buy subscriptions for themselves, so the payer and the user are the same person. Your authentication checks if the current user has a subscription, and everything works. Then a team signs up. The CTO purchases a subscription for their engineering department. Ten engineers need access, but only the CTO has a billing relationship with you. Your authentication logic breaks because the engineers don't have subscriptions in their names.
The quick fix is custom code. You create a mapping table that says "these ten user IDs belong to this subscription." You check that table during authentication. It works until the team grows to twenty, someone leaves, and the CTO asks why they're still paying for departed employees. Now you need admin interfaces for the CTO to manage team membership, and your subscription logic is scattered across multiple systems.
Grantee groups solve this properly from the start. The concepts are distinct and the relationships are explicit, which means your code stays simple even as team complexity grows.
## The Three-Part Model
Understanding grantee groups requires distinguishing three concepts that traditional billing systems conflate: owners, grantees, and groups.
The **owner** identifies who holds the subscription—typically an organisation ID, team ID, or user ID from your system. This is the identifier your application passes when looking up subscription status or recording metered usage. It represents whoever is responsible for the billing relationship: the company that purchased, the team that subscribed, or the individual user. When subscriptions renew, cancel, or upgrade, the owner is the party involved.
**Grantees** are the individuals who receive access to your product through the subscription. They don't have their own billing relationship; instead, they derive access from membership in a group that's associated with the subscription. A grantee might be an employee using company-licensed software, a team member added by an administrator, or a collaborator invited to a shared workspace.
The **group** connects owners and grantees. It's the container that holds grantees and associates with subscriptions. When you check whether a user has access to a feature, you're really asking whether they're a grantee in a group that has an active subscription with the appropriate entitlements.
This separation might seem like unnecessary complexity, but it reflects how teams actually work. The person with the corporate credit card isn't the same as the people using the product. The list of users changes more frequently than the billing relationship. And the same owner might have multiple groups with different subscriptions for different purposes.
## How Access Flows Through the System
When a grantee tries to access a feature, the access check follows a clear path: retrieve the groups the grantee belongs to, find the subscriptions associated with those groups, and check whether any of those subscriptions include entitlements for the requested feature.
In practice, this means your authentication middleware calls a single function with the grantee's ID and the entitlement they need. Salable handles the traversal from grantee to groups to subscriptions to entitlements and returns a yes or no answer. Your code doesn't need to understand the relationship model; it just asks "can this user do this thing?" and gets a definitive response.
This design scales elegantly. A grantee might belong to multiple groups, perhaps their department group and a cross-functional project group. Each group might have different subscriptions with different entitlements. The access check considers all paths and grants access if any valid path exists.
The performance implications are handled at the infrastructure level. Salable caches the relationship graph so that access checks are fast even for complex group structures. When group membership changes, the cache updates automatically. You don't need to implement caching logic or worry about stale permissions.
## Managing Team Membership
The owner needs to control who belongs to their group, but that control shouldn't require navigating your billing interface for every change. Grantee groups support self-service membership management through APIs and embeddable components.
The simplest pattern is an invitation flow. The owner generates an invitation link or enters email addresses. Invited users accept and become grantees in the group. No manual provisioning on your side, no webhook handlers to map users to subscriptions, no administrative overhead.
For organisations with existing identity providers, grantee groups can sync with external directories. When an employee joins or leaves in the company's HR system, their group membership updates accordingly. The billing subscription doesn't change; only the list of grantees who derive access from it does.
Seat limits enforce themselves through the group. If a subscription includes 50 seats, the group can have at most 50 grantees. Attempting to add more fails with a clear error indicating the seat limit. The owner can either remove existing grantees or upgrade their subscription to add more seats. This enforcement happens at the group level, not in your application code.
## Handling Complex Organizational Structures
Real organisations rarely fit a simple hierarchy. Departments, teams, projects, and temporary working groups create overlapping structures that traditional per-seat billing can't model.
Grantee groups handle this complexity through multiple groups per owner and multiple group memberships per grantee. A company might have a department-level group for standard access and project-specific groups for specialized features. An employee belongs to their department group and gets added to project groups as needed. Each group can have its own subscription or share entitlements, depending on how you structure your plans.
Consider an enterprise with three departments, each needing your product. Rather than one massive subscription, they might prefer three separate subscriptions with independent billing, budgeting, and administration. Three groups, three subscriptions, one owner at the corporate level. Department administrators manage their own grantees without accessing other departments' groups.
Alternatively, a company might want unified billing but department-level usage tracking. One subscription, three groups, with reporting segmented by group. The billing administrator handles payments while department managers handle membership. The flexibility is in how you model the relationships, not in custom code you write.
## The Developer Experience
For engineering teams implementing team subscriptions, grantee groups dramatically simplify the integration. The Salable SDK provides methods for the common operations: checking access, managing grantees, creating invitations, and querying group membership.
Access checks are the most frequent operation and the simplest to implement. A single API call returns whether a grantee has a specific entitlement through any of their group memberships. You don't need to understand the underlying subscription structure; the answer is already resolved.
```javascript
const hasAccess = await salable.entitlements.check({
granteeId: currentUser.id,
entitlement: 'advanced-analytics'
});
```
For administrative interfaces, the SDK provides methods to list group members, add and remove grantees, and check available seat capacity. These power the team management UI that owners use, whether you build custom interfaces or embed Salable's components.
When subscriptions change, webhooks notify your application. A new subscription might mean creating a group. An upgrade might mean increased seat capacity. A cancellation might mean revoking access. These events arrive in real-time, allowing your application to respond immediately rather than polling for changes.
## Migrating Existing Team Implementations
If you already have team subscriptions with custom membership logic, migrating to grantee groups means mapping your existing data model to the new concepts.
The migration typically involves three steps. First, identify your current owners, those who have billing relationships in your system. Create corresponding owners in Salable with the same identifiers. Second, create groups for each team structure you're currently tracking manually. Third, add your existing team member mappings as grantees in the appropriate groups.
Once migrated, you can remove your custom membership tables and the code that queries them. Access checks route through Salable instead of your database. Team management happens through the grantee group interfaces instead of your admin panels. The reduction in custom code translates directly to reduced maintenance burden.
For gradual migration, you can run both systems in parallel during a transition period. Check your existing tables first and fall back to grantee groups, or vice versa. This approach lets you validate the new system before fully committing.
## Scaling to Enterprise
Grantee groups aren't just for small teams; they're designed to handle enterprise scale. Organisations with thousands of employees and complex hierarchies use the same primitives as ten-person startups. The difference is in configuration, not code.
Bulk operations handle large-scale changes efficiently. Importing hundreds of grantees from a CSV or syncing thousands of users from an identity provider happens through optimised bulk APIs rather than individual requests. The system is built for enterprise data volumes.
Security controls at the group level let you implement principle of least privilege. Different groups can have different entitlements, and users only receive the entitlements from groups they belong to. Removing someone from a high-privilege group immediately revokes those entitlements even if they remain in other groups.
## The Simplicity of Explicit Relationships
The fundamental insight behind grantee groups is that explicit modeling beats implicit inference. When relationships between payers, users, and access are explicit in your data model, everything else gets simpler. Access checks become lookups rather than computations. Team management becomes data updates rather than business logic. Scaling becomes capacity rather than architectural rework.
Your application code asks simple questions and gets simple answers. Can this user access this feature? Yes or no. How many seats are available in this group? A number. Who are the members of this group? A list. The complexity of team subscriptions is handled in the infrastructure where it belongs, not scattered throughout your application.
Per-seat pricing still sounds simple, because with grantee groups, it actually becomes simple. The billing owner, the access recipients, and the group that connects them are all first-class concepts with explicit relationships. Your implementation reflects reality instead of working around a data model that assumes every payer is also a user.
The CTO who buys 50 seats can manage their team without understanding your billing system. Engineers get access without needing subscription records in their names. And when someone leaves, removing them from the group is all it takes. The subscription continues, the seat frees up, and the access revokes automatically. That's team subscriptions done right.
---
### Stripe Webhooks Without the Pain: How Salable Handles the Hard Parts
Source: https://beta.salable.app/blog/beta-features/stripe-webhooks-without-pain
# Stripe Webhooks Without the Pain: How Salable Handles the Hard Parts
The Stripe webhook documentation makes it look easy: receive an event, verify the signature, process the payload. Three steps. What the documentation doesn't mention is what happens when your server is down during a critical event. Or when the same event arrives twice. Or when a subscription update arrives before the subscription created event. Or when your database transaction fails mid-processing and Stripe retries the webhook while you're in an inconsistent state. Production webhook handling is a reliability engineering problem that most teams underestimate until they're debugging lost subscriptions at 2 AM. Salable's infrastructure handles these edge cases with a battle-tested pipeline that doesn't drop events.
Every developer who's integrated Stripe has a war story. The customer who was charged but never got access because the webhook handler crashed after charging but before provisioning. The duplicate charge that occurred because the handler didn't check for idempotency. The subscription that shows as active in Stripe but cancelled in the application because events arrived out of order. These bugs are insidious because they're intermittent, they affect paying customers, and they're hard to reproduce.
## The Hidden Complexity
Understanding why webhooks are hard requires examining what can go wrong at each stage.
**Network reliability** is the first concern. Stripe sends a webhook; your server receives it; the response returns to Stripe. Any of these network hops can fail. Packets get lost. Connections time out. DNS hiccups. Your server might receive the webhook but Stripe might not receive your acknowledgment, causing a retry of an event you already processed.
**Server availability** matters because webhooks arrive on Stripe's schedule, not yours. A deployment that restarts your application, a spike in traffic that overwhelms your server, or a cloud provider hiccup can all cause missed webhooks. Stripe will retry, but the retry schedule spans hours, and your customers shouldn't wait hours for their purchase to process.
**Processing failures** occur even when the webhook arrives successfully. Your handler might throw an exception mid-processing. The database connection might fail during a write. An external API your handler calls might time out. If processing fails after some side effects but before others, you're left in an inconsistent state.
**Duplicate delivery** is explicitly documented by Stripe but often ignored by developers. "Your webhook endpoints might occasionally receive the same event more than once." Occasionally sounds rare, but at scale, occasional adds up. Every handler must be idempotent, treating the second delivery of an event the same as the first.
**Event ordering** can't be guaranteed. A subscription might be deleted before the corresponding creation event arrives. An invoice payment event might arrive before the invoice creation event. Events that seem sequential can arrive in any order, and your handler must cope.
## The Idempotency Requirement
Every webhook handler must be idempotent, meaning processing the same event twice produces the same result as processing it once. This requirement sounds simple but affects how you design every handler.
The naive approach to handling a subscription creation event might be: create a user record, create a subscription record, send a welcome email. If this handler runs twice, you might create duplicate users, duplicate subscriptions, or send duplicate emails. None of these outcomes are acceptable.
The idempotent approach tracks event IDs. Every Stripe webhook carries a unique event identifier, and the first thing a reliable handler does is check whether that identifier has been processed before. If it has, the handler returns success immediately—the work is already done. If the event is new, processing proceeds normally, and the event ID is recorded upon completion. This single check at the event level prevents duplicate users, duplicate subscriptions, and duplicate emails regardless of how many times Stripe delivers the same webhook.
Implementing idempotency correctly requires careful thought about what "already processed" means. An event might have been partially processed before a failure. Your checks need to verify that all effects were applied, not just that processing began.
## Event Ordering Challenges
Stripe sends events in roughly chronological order, but "roughly" isn't a guarantee. Network latency, retry timing, and parallel event generation can all cause events to arrive out of order.
Consider subscription creation and immediate update. A customer creates a subscription and immediately changes a setting. Stripe generates `subscription.created` and then `subscription.updated`. Due to network timing, the update arrives first. Your handler tries to update a subscription that doesn't exist yet, fails, and returns an error. Stripe retries the update later; maybe the creation has processed by then, maybe not.
The robust solution is to read from Stripe's API when processing each event. Instead of relying on the event payload, fetch the current state of the entity directly. That way you always have the latest data regardless of what order events arrive.
## Retry Logic and Exponential Backoff
Stripe retries failed webhooks on an exponential backoff schedule: 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and then hourly for up to 3 days. This schedule means that a server outage lasting an hour might delay event processing for hours, and some events might not be delivered until the next day.
For critical events like subscription creation or payment success, this delay is unacceptable. Customers expect immediate access when they pay. Waiting hours because your server happened to be deploying at the wrong moment is a poor experience.
## Database Transaction Hazards
Webhook handlers often need to update multiple database records together. A subscription creation might create user, subscription, and entitlement records. If any write fails, the entire operation should roll back.
Without transactions, a failure mid-processing leaves your database in a partial state—user created but subscription missing, or subscription created but entitlements not granted. These inconsistencies are difficult to detect and painful to debug. When Stripe retries, your idempotency checks might see existing data and skip steps, leaving the partial state permanently unresolved.
With transactions, all your writes succeed or fail together—but this creates new problems. Long-running transactions hold database connections and block subsequent requests. As handlers queue up, failures cascade—each slow transaction makes the next one slower. Eventually, processing takes longer than Stripe's timeout, and Stripe assumes failure and retries while you're still working on the first attempt.
## Queue-Based Processing
The solution is to separate receipt from processing entirely. When a webhook arrives, your endpoint does three things: validate the signature, write the event to a durable queue, and return success to Stripe. Nothing else. The entire operation takes milliseconds and never touches your main database.
Processing happens separately, driven by workers that pull events from your queue. These workers can take whatever time they need—there's no Stripe timeout looming. If a worker fails mid-processing, the event stays in the queue for another attempt. You control the retry schedule: immediate retries for transient failures, exponential backoff for persistent problems, and configurable limits before giving up. Your transactions can run as long as they need to because they're not blocking Stripe's connection.
This architecture is the right answer, but implementing it yourself is substantial work. You need to choose and operate a queue technology—Redis, RabbitMQ, SQS, or similar—and ensure it's configured for durability so events survive restarts and crashes. You need workers that process reliably, handle failures gracefully, and don't duplicate work when retrying. You need visibility into how full the queue is, how long processing takes, and how often it fails so you can spot problems before customers do. You need alerting when things go wrong and tooling to investigate when they do.
Most teams underestimate this. What starts as "just add a queue" becomes weeks of infrastructure work, and the system requires ongoing operational attention. It's the right architecture, but it's not simple to build or maintain.
## Dead Letter Queues for Failed Events
Some events can't be processed no matter how many times they're retried. An event type your handler doesn't recognise. A payload that fails validation for unknown reasons.
These events shouldn't retry forever. After a configured number of attempts, they should route to a dead letter queue for human review. The queue captures the event payload, the error messages from processing attempts, and timestamps. An operator can examine failed events, determine the cause, fix the underlying issue, and reprocess if appropriate.
Building a dead letter queue isn't complicated, but building the tooling around it—dashboards, alerting, reprocessing workflows—takes time.
## The Alternative
Teams that build their own Stripe integration face a choice: build production-grade webhook handling, or accept the reliability gaps.
Building properly takes weeks of engineering time and ongoing maintenance. For most teams, this is a poor use of engineering resources that could go toward product features.
Accepting reliability gaps means occasional customers who pay but don't get access, subscriptions that fall out of sync between your application and Stripe, and 2 AM debugging sessions when critical events are lost. The cost shows up in support tickets, churn, and engineering distraction.
Salable provides the solution: reliable infrastructure without building it yourself. Your integration stays simple while the hard parts happen behind the scenes.
The Stripe webhook docs make it look easy. Three steps: receive, verify, process. In production, those three steps explode into dozens of edge cases, failure modes, and reliability requirements. Salable handles all of it so you can focus on building your product. Your customers subscribe and get access, every time, without you ever having to debug a lost webhook.
---
### Multi-Currency Support: Sell Globally from Day One
Source: https://beta.salable.app/blog/beta-features/multi-currency-support
# Multi-Currency Support: Sell Globally from Day One
Your first international customer just signed up, and they're asking why they have to pay in dollars. Currency conversion fees eat into their budget, and the psychological friction of foreign pricing makes your product feel less accessible. You could calculate exchange rates manually, but they fluctuate daily, and your billing system lacks currency-handling logic anyway. Multi-currency support removes this barrier. Configure prices in the currencies your customers use, and let the checkout flow present the right price automatically.
The frustration shows up in support tickets and abandoned checkouts. A prospect in Germany sees \$49/month and has to do mental math to understand what that means in euros. Then they wonder about the conversion fee their bank will charge. Then they question whether a product that can't be priced in their currency is really built for their market. Each friction point reduces the likelihood they'll complete the purchase.
This isn't about convenience; it's about credibility. When you display prices in a customer's local currency with round, intentional numbers, you signal that you've thought about their market. You're not an American company grudgingly accepting foreign credit cards. You're a global business that serves customers where they are.
## Why Conversion Isn't Enough
The simplest approach to international pricing is letting your payment processor handle currency conversion. The customer pays in their local currency, and Stripe or your processor converts it to your settlement currency at the current exchange rate plus a conversion fee.
This approach works technically, but it fails commercially. The customer sees a price like EUR 45.23, which looks accidental rather than intentional. The amount changes with exchange rate fluctuations, so a customer who checked your pricing yesterday might see a different number today. And the conversion fee, typically 1-2%, either eats into your margin or gets passed to the customer as an additional line item.
Intentional local pricing looks different. You set EUR 49/month because that's a clean, marketable price in the European market. The price doesn't fluctuate with exchange rates because it's not derived from your USD pricing; it's set independently for that currency. Customers see the same number every time they visit your pricing page, and the number feels deliberate.
The psychological research on pricing supports this approach. Customers respond more positively to round numbers and consistent pricing than to mathematically derived amounts. The conversion fee savings are real but secondary to the trust and professionalism that intentional pricing conveys.
## Setting Prices Across Currencies
Multi-currency support means configuring distinct prices for each currency you want to support. This isn't a multiplier applied to your base currency; it's independent pricing for each market.
The independence matters because markets have different value perceptions. What customers are willing to pay in the United States, Europe, and Southeast Asia varies by local purchasing power, competitive alternatives, and cultural expectations. A \$99/month price might translate to EUR 99, GBP 79, or JPY 9,900, not because of exchange rates but because those are the right prices for those markets.
When setting international prices, consider purchasing power parity as a starting point but not a rigid formula. PPP suggests what an equivalent amount of money buys in different economies, but software pricing doesn't follow PPP perfectly. Your costs are mostly fixed regardless of where customers are located, and customers in lower-PPP markets may still pay premium prices for products that deliver premium value.
Research your competitive landscape in each market. What do similar products charge in euros? In pounds? In yen? Price relative to local alternatives, not just relative to your own USD pricing.
## Automatic Currency Detection
Configuring prices in multiple currencies only helps if customers see the right one. Salable's checkout flow detects the customer's likely currency based on their browser locale and IP geolocation, presenting prices in that currency by default.
The detection isn't deterministic. A customer in France using an English-locale browser might be an expat who prefers EUR or an American traveller who prefers USD. The checkout should show the detected currency but let customers change it if needed. A simple currency selector, often displayed near the price, lets customers choose their preferred option without hunting through settings.
For products where the customer's location has compliance implications, you might require certain currencies for certain jurisdictions. EU customers paying for services with VAT implications might need to transact in EUR regardless of their preference. Your checkout flow should handle these edge cases without breaking the general experience.
## Managing Exchange Rate Risk
When you set independent prices in multiple currencies, you take on exchange rate risk. The EUR 49 you charge today might be worth more or less in USD terms next month, depending on how currencies move. For small volumes, this risk is negligible. For significant international revenue, it's worth understanding.
Exchange rate fluctuations average out over time for most businesses. EUR strengthens against USD one quarter; it weakens the next. If your international revenue is diversified across multiple currencies, the fluctuations partially cancel each other out.
If you have substantial revenue concentration in a volatile currency, consider periodic price adjustments. You don't need to track exchange rates daily, but an annual review of international pricing against your base currency ensures you're not drifting too far from your intended margins.
Some businesses prefer to keep international prices loosely tied to a base currency, adjusting them when exchange rates move beyond a threshold. If EUR moves more than 10% against USD, you might update your EUR pricing accordingly. This hybrid approach captures most of the benefits of local pricing while limiting exchange rate exposure.
## Tax and Compliance Considerations
International sales introduce tax complexity. Different jurisdictions have different rules for digital services tax, VAT, GST, and sales tax. Multi-currency support is necessary for serving these markets but doesn't automatically solve tax compliance.
The good news is that modern billing infrastructure handles much of this complexity. Salable integrates with Stripe's tax calculation, which determines the appropriate tax rate based on the customer's location and the nature of the service. The customer sees a price including tax where required, and the correct amount is collected and reported.
What you need to ensure is that your prices are set appropriately for tax-inclusive or tax-exclusive presentation. In the EU, B2C prices are typically VAT-inclusive, so your EUR 49 price should already include the applicable VAT. In the US, prices typically display without sales tax, which is added at checkout. Configure your prices for each currency with the local presentation convention in mind.
## Rolling Out Multi-Currency
You don't need to support every currency from day one. Start with the currencies that serve your existing and near-term target markets.
For most SaaS businesses, a pragmatic starting set includes USD for the Americas, EUR for the Eurozone, GBP for the UK, and potentially AUD for Australia and CAD for Canada. These five currencies cover the majority of English-speaking B2B SaaS purchases. Expand from there based on where you see demand.
When adding a new currency, audit your entire pricing surface. Every plan, every line item, every add-on needs a price in the new currency. Partial coverage creates confusing experiences where some prices appear localised, and others fall back to your default currency.
Test the checkout flow thoroughly for each currency. Verify that the correct price displays, the tax calculation works, and the payment processes without issues. Currency-related bugs often appear only with specific currency/country/tax combinations, so test realistic scenarios rather than just the happy path.
## Communicating International Availability
Multi-currency support signals international readiness, so communicate it explicitly. Your pricing page should show that you serve customers globally, not just display a currency selector that visitors might miss.
Consider showing prices in the visitor's detected currency by default while making it easy to see other options. Prices shown in EUR. Also available in USD, GBP, and more, this tells European visitors that you've considered their market while indicating broader international support.
For enterprise sales, multi-currency support simplifies procurement. Global companies often prefer paying in their headquarters' currency for accounting consistency. Being able to invoice in USD, EUR, or GBP, based on customer preference, removes a friction point in procurement.
## Beyond the Transaction
Multi-currency affects more than checkout. Your customer portal should display subscription details in the currency the customer is paying. Invoices should be denominated in the transaction currency. Usage reports and billing histories should be consistent with what the customer actually pays.
This consistency extends to your internal reporting. You'll want to track revenue in both local currencies and a normalised base currency for comparison. Understanding that EUR revenue grew 20% while USD revenue grew 15% requires seeing both the local currency figures and their base currency equivalents.
Salable's reporting provides both views. You can see revenue in transaction currencies to understand each market's performance, and you can see normalised revenue for overall business health. The conversion happens at consistent rates for reporting purposes, separate from the actual transaction values.
## The Broader Opportunity
Multi-currency support isn't just about removing friction for existing international interest. It's about expanding your addressable market. Customers who wouldn't consider a product priced only in a foreign currency might happily purchase when they see local pricing.
The SaaS market is global, and geographic constraints are largely artificial. A project management tool is just as useful in Berlin as in Boston. A developer platform serves engineers everywhere. Multi-currency support lets you capture that global opportunity without building separate products for each market.
Start with the currencies your current customers need. Expand to the markets you want to grow into. Set intentional prices that reflect local value, not just mathematical conversions. And let the checkout flow handle detection and selection so customers see the right price without effort.
Your next customer might be anywhere in the world. Make sure your pricing page is ready to greet them in their language, which, when it comes to money, means their currency.
---
### Choosing Your First Pricing Model: Flat-Rate vs. Per-Seat vs. Usage-Based
Source: https://beta.salable.app/blog/saas-startup-guides/choosing-pricing-model
# Choosing Your First Pricing Model: Flat-Rate vs. Per-Seat vs. Usage-Based
Pricing advice is everywhere, but most of it skips the fundamental question: how do your customers measure the value they get from your product? If they value predictability, flat-rate wins. If value scales with team size, per-seat makes sense. If usage varies wildly between customers, metering aligns your revenue with their outcomes.
The model you choose shapes your revenue trajectory, your sales motion, and your engineering requirements. Understanding the tradeoffs now prevents painful migrations later.
## The Value Metric Question
Before comparing pricing models, you need to answer one question: what makes your product more valuable to one customer than another? The answer isn't always obvious, and getting it wrong leads to pricing that frustrates customers and leaves money on the table.
Consider a document collaboration tool. One customer might be a solo consultant who creates a few documents per month. Another might be a fifty-person team generating hundreds of documents daily. The solo consultant and the team both use the same software, but they derive vastly different value from it. Your pricing model should reflect this difference.
The value metric is the unit that captures this variation. For collaboration software, it might be team members. For an API, it might be requests. For analytics software, it might be events tracked. The right pricing model follows naturally once you've identified the right value metric.
Some products have obvious value metrics. A CRM that stores customer records creates more value as the sales team grows, making per-seat pricing intuitive. An email sending service creates more value as volume increases, making usage-based pricing logical. Sometimes the right choice is simply flat-rate—customers value predictability, and there's power in keeping it simple.
Other products have less obvious value metrics, or multiple competing ones. A project management tool could price by users, by projects, by storage, or simply by feature access. What matters is what customers are willing to pay for your service.
## Flat-Rate Pricing: Simplicity as a Feature
Flat-rate pricing charges every customer the same amount, regardless of how much they use the product or how many people access it. This model works when usage doesn't correlate meaningfully with value, or when customers prioritise predictability above all else.
The appeal starts with simplicity. Customers know exactly what they'll pay, making purchase decisions easier. There's no mental math calculating whether adding a team member is worth the incremental cost, no anxiety about usage spikes triggering unexpected charges. The price is the price.
This simplicity extends to your implementation and operations. You don't need to track usage, allocate seats, or calculate prorated charges for plan changes. Invoicing is straightforward. Revenue is predictable. Customer support doesn't field questions about billing details.
Flat-rate pricing works particularly well for products that serve as tools rather than platforms. A grammar checking extension, a backup service, or a personal productivity app often fits this model. The product either works or it doesn't; value doesn't scale meaningfully with usage.
The downside is that flat-rate pricing compresses your revenue range. Your most valuable customers pay the same as casual users, leaving money on the table. Conversely, users who barely touch the product might feel overcharged, increasing churn among your least engaged segment.
The revenue ceiling is also lower. Growing revenue requires acquiring new customers rather than expanding existing accounts. When a customer's needs grow, you don't benefit unless they need a fundamentally different product that justifies a higher tier.
Flat-rate pricing suits products where simplicity is a competitive advantage, where the customer base is relatively homogeneous in their usage patterns, and where the sales motion is self-serve rather than sales-assisted.
## Per-Seat Pricing: Revenue That Scales with Teams
Per-seat pricing charges based on the number of users who access the product. This model works when your product's value genuinely multiplies with team size, and when the team-based nature of the product makes seat counts a natural unit.
The appeal is alignment between your revenue and customer growth. When a company hires employees who need your tool, your revenue grows automatically. Account expansion happens organically, without sales effort.
Per-seat pricing also creates natural upgrade pressure. When a company is close to their seat limit, adding one more person triggers a billing change. This creates regular decision points where customers consider their plan level, often leading to discussions about features or tiers they might upgrade to.
The implementation complexity is moderate. You need to track which users have active seats and enforce limits. You need to handle the administrative experience of adding and removing seats. You need to decide whether customers pay for a fixed seat commitment or only for the seats they're actively using. These aren't trivial concerns, but they're well-understood problems with established solutions.
Per-seat pricing works well for collaboration and communication tools, where more users literally means more value. Think team chat applications, design collaboration platforms, or shared workspace tools. The seat count directly reflects how many people benefit from the product.
The downside is that per-seat pricing can discourage adoption. Customers might limit seat allocation to reduce costs, leaving potential users without access. Shadow IT emerges as teams share credentials to avoid charges. Your product might deliver more value with broader adoption, but the pricing model discourages exactly that.
Per-seat pricing also doesn't capture value differences between users. A power user who lives in your tool pays the same as someone who logs in once a month. An executive seat might cost the same as an intern's seat, despite vastly different value delivered.
Some products address this by creating user tiers with different pricing. Viewer seats might cost less than editor seats; admin seats might include premium features. This refinement captures value differences but adds complexity to your pricing page and purchasing flow.
Per-seat pricing suits products where collaboration is central, where more users genuinely means more value, and where seat counts align with how customers think about their investment.
## Usage-Based Pricing: Aligning Cost with Consumption
Usage-based pricing charges customers based on how much they consume, whether that's API calls, data processed, messages sent, or any other measurable unit. This model works when usage varies dramatically between customers and correlates with the value they receive.
The appeal is fairness and flexibility. Customers who use more pay more; customers who use less pay less. This eliminates the objection that a product costs too much for limited use, because limited use costs proportionally less. New customers can start small and expand as they succeed, lowering the barrier to adoption.
Usage-based pricing also creates natural revenue expansion. As customers grow their usage, your revenue grows without sales intervention. A customer who processes ten thousand events might grow to process a million events, and your revenue scales accordingly. The growth isn't capped by a seat count or tier limit; it's limited only by customer success.
From a positioning perspective, usage-based pricing signals confidence in your product's value. You're saying that the more customers use your product, the more value they receive, and you're willing to stake your revenue on that relationship. This resonates with customers who want pricing tied to outcomes rather than arbitrary limits.
The implementation complexity is significant. You need to measure usage accurately and in real-time, expose that data so customers can monitor their consumption, handle billing cycles and usage aggregation, and communicate pricing clearly despite the inherent unpredictability.
Usage-based pricing works well for infrastructure products, APIs, and platforms where consumption varies by orders of magnitude between customers. The customer processing a hundred API calls per day has fundamentally different economics than the customer processing a million. Flat-rate pricing either excludes the small customer or undercharges the large one.
The downside is unpredictability, both for you and your customers. Customers may hesitate to adopt a product when they can't predict monthly costs. Finance teams struggle to budget for variable expenses. That uncertainty creates friction in the purchasing process.
Customers may also constrain their usage to control costs, even when using more would benefit them. A developer might cache aggressively or batch requests to reduce API charges, potentially degrading their user experience to save money. The pricing model can work against adoption in ways that hurt both parties.
Usage-based pricing suits products where consumption genuinely varies by orders of magnitude, where usage correlates strongly with customer value, and where customers accept variable costs as a tradeoff for flexibility.
## Hybrid Models: Combining Approaches
Most mature SaaS products eventually adopt hybrid models that combine elements of multiple approaches. Understanding the pure models helps you design hybrids that capture their benefits while mitigating their drawbacks.
The most common hybrid is a base fee plus usage. Customers pay a flat monthly charge that includes some baseline access, then pay additional fees for consumption beyond that baseline. This structure provides revenue predictability for you and cost predictability for customers, while still allowing revenue to scale with intensive usage.
Another hybrid is per-seat pricing with usage limits. Each seat includes a certain allocation of resources, and exceeding those allocations triggers additional charges. This preserves the simplicity of per-seat billing while capturing additional value from power users.
Tiered usage pricing is another variant. Rather than charging linearly per unit, you offer usage brackets with different per-unit costs. A customer might pay \$0.10 per request up to ten thousand requests, then \$0.05 per request beyond that. This creates volume incentives while maintaining usage alignment.
The risk with hybrid models is complexity. Every additional variable makes the purchasing decision harder. Customers struggle to predict costs, finance teams struggle to budget, and your sales team struggles to explain the model clearly. Simplicity has real value; don't sacrifice it without good reason.
## Making the Decision
The right pricing model for your product depends on answers to several questions. How do customers perceive value from your product? Does value scale with users, with usage, or remain roughly constant? What does your competitive landscape look like? How do customers expect to buy products in your category?
If you're genuinely unsure, start with flat-rate pricing. It's the simplest to implement, the easiest to explain, and the fastest to launch. You can add complexity later; removing it is much harder.
If your product is inherently collaborative and team-based, per-seat pricing probably makes sense. Customers already think about the product in terms of team members, so pricing by seat feels natural.
If your product serves customers of all sizes, and usage varies dramatically between them, usage-based pricing aligns your revenue with customer success. Just be prepared for the implementation complexity and customer education requirements.
Whatever you choose, remember that your initial pricing model isn't permanent. Most successful SaaS companies have changed their pricing multiple times as they learned more about their customers and markets. The goal isn't perfection on day one; it's to get something working well enough to generate revenue and learning.
Choose the model that matches your value metric today, implement it as simply as possible, and plan to iterate based on what customers teach you.
---
_Not sure which model fits your product? Salable supports flat-rate, per-seat, and usage-based pricing out of the box, so you can experiment without re-architecting. Explore the [pricing models documentation](https://beta.salable.app/docs/products-and-pricing) to see how each approach works in practice._
---
### Introducing Tiered Pricing: Volume Discounts Made Simple
Source: https://beta.salable.app/blog/beta-features/introducing-tiered-pricing
Every growing SaaS hits the same wall: your biggest customers want volume discounts, but your billing system only supports one price per unit. You end up with spreadsheets tracking custom deals, manual invoice adjustments, and a pricing page that lies to your best customers. Tiered pricing solves this by encoding volume discounts directly into your pricing model. Buy more, pay less per unit, automatically calculated and invoiced without human intervention.
The request usually comes from a sales call. A prospect loves your product and wants to roll it out across their organisation, but they balk at paying the same per-user rate for 500 seats that they'd pay for five. You want to accommodate them because larger deals mean better unit economics for you too. But your billing system doesn't support volume discounts, so you create a custom plan, track it in a spreadsheet, and hope you remember to honour the special pricing when the invoice goes out.
This approach doesn't scale. By the time you have a dozen custom deals, you're spending hours each month reconciling billing against your spreadsheet of promises. Worse, you can't publish volume discounts on your pricing page because your system can't calculate them automatically. Potential customers who would self-serve at higher volumes never see that option.
Tiered pricing automates what you're already doing manually. You define price brackets based on quantity, and the billing system calculates the correct charge for any order size. A customer buying 10 units pays one rate; a customer buying 100 units pays a lower rate per unit; a customer buying 1,000 units pays lower still. No spreadsheets, no manual adjustments, no special deals that slip through the cracks.
## How Tiered Pricing Works
The fundamental concept is straightforward: you divide quantities into brackets, and each bracket has its own unit price. When a customer's usage or purchase quantity falls into a bracket, that bracket's pricing applies. The complexity lies in _how_ the pricing applies, which brings us to the critical distinction between graduated and volume tiers.
Understanding this distinction matters because choosing the wrong model can either leave money on the table or create pricing cliffs that confuse customers. The names sound similar, but they produce dramatically different invoices.
With **graduated tiers**, each bracket charges its own rate for the units within that bracket. Think of it like income tax brackets in many countries. If your first tier covers units one through 50 at $10 each, and your second tier covers units 51 through 100 at $8 each, a customer buying 75 units pays $10 for the first 50 units plus $8 for the next 25 units, totalling $700.
With **volume tiers**, the qualifying bracket applies to _all_ units. Using the same brackets, a customer buying 75 units falls into the second tier, so they pay $8 for all 75 units, totalling $600. The per-unit rate they qualify for applies universally rather than bracket by bracket.
The mathematical difference is significant. In the graduated example, the customer pays $700. In the volume example, they pay $600. That's a 14% difference from the same tier structure, just applied differently.
## Choosing Between Graduated and Volume Tiers
Graduated tiers suit most SaaS pricing scenarios because they create smooth cost curves. As customers grow, their costs increase proportionally with a gentle downward slope in per-unit pricing. There are no sudden jumps or counterintuitive moments where buying more actually costs less.
Volume tiers create discount cliffs, which can be strategically useful but require careful design. Consider what happens at tier boundaries with volume pricing. If units one through 100 cost $10 each and units 101 through 200 cost $8 each, a customer buying 100 units pays $1,000, but a customer buying 101 units pays $808. Buying one more unit saves them $192. This cliff effect can drive behaviour you want, like pushing customers to commit to higher volumes, but it can also create support headaches when customers game the system or feel tricked.
The general guidance is to default to graduated tiers unless you have a specific reason to create discount cliffs. Graduated tiers are easier to explain, less prone to edge-case confusion, and still reward volume purchases without the counterintuitive pricing moments.
## Designing Your Tier Structure
The number of tiers and where you set the boundaries depends on your customer distribution and business goals. Too few tiers and you're not rewarding volume growth. Too many tiers and your pricing page becomes unreadable.
Most successful tiered pricing implementations use three to five brackets. The first tier covers your typical individual or small team customer. The middle tiers capture growing businesses. The top tier, often labelled "Enterprise" or with custom pricing, handles your largest accounts.
Setting boundaries requires looking at your actual customer data. Where do customers naturally cluster? If most customers have between one and ten users, but you have a meaningful segment with 50 to 200 users, and occasional accounts with 500 or more, your tier boundaries might fall at 10, 50, 200, and 500+ units.
The discount progression matters too. A 5% discount per tier feels minimal; customers might not notice it. A 50% discount per tier might be unsustainable for your margins. Most tiered pricing lands somewhere between 10% and 25% discount per tier, with larger jumps at higher volumes where your marginal costs are genuinely lower.
## Implementation Without the Headaches
The promise of tiered pricing is automation. You configure your tiers once, and every invoice calculates correctly regardless of customer size. But achieving this automation requires a billing system that supports tiered pricing natively.
In Salable, you configure tiered pricing directly on your line items. You define each tier with its quantity range and unit price, choose between graduated and volume calculation, and the system handles everything else. When a customer's subscription renews or their metered usage gets invoiced, the tier calculation happens automatically. If they grow from 75 seats to 150 seats mid-cycle, the invoice prorates correctly across the applicable tiers.
This native support matters because retrofitting tiered pricing onto a system that doesn't support it creates fragile workarounds. You end up with webhook handlers that intercept invoices and recalculate totals, or duplicate products that represent different volume levels. These workarounds break when Stripe changes their API or when you need to handle edge cases like mid-cycle upgrades.
## Communicating Tiered Pricing to Customers
Automated calculation only helps if customers understand what they're being charged. Tiered pricing can confuse buyers who expect a single unit price, so your pricing page and checkout flow need to explain the model clearly.
The most effective approach shows both the per-unit price at each tier and a calculated example at common quantities. A table showing your tier brackets gives customers the raw information, while calculated examples like "50 users: $400/month" and "200 users: $1,200/month" make the savings tangible.
For self-serve checkout, showing the calculated total based on the quantity they've entered removes uncertainty. The customer selects 75 seats, and the checkout shows them exactly what they'll pay, broken down by tier if you're using graduated pricing. No surprises on the invoice.
For sales-assisted deals, tiered pricing gives your sales team a framework for volume discounts that doesn't require manager approval for every deal. The customer qualifies for the published tier, period. This consistency builds trust and speeds up the sales cycle.
## When Tiered Pricing Isn't the Answer
Tiered pricing works best when you're selling countable units at predictable volumes. Per-seat licensing, API calls, storage gigabytes, and similar metrics fit the model naturally. But not every pricing situation calls for tiers.
If your value delivery is genuinely flat regardless of usage, tiers add complexity without benefit. A product that costs the same to serve whether the customer has one user or a hundred might be better priced as a flat monthly fee with an upper limit.
If customer value varies dramatically by use case rather than volume, you might need separate products rather than volume tiers. A customer processing financial transactions and a customer tracking inventory might both use your API, but the value you deliver differs enough that volume alone doesn't capture it.
And if your largest customers need genuinely custom arrangements involving SLAs, dedicated infrastructure, and bespoke integrations, those belong in enterprise sales conversations rather than self-serve tiered pricing. Tiers work for automated volume discounts; they don't replace relationship-driven enterprise deals.
## Moving Forward with Tiered Pricing
The path from manual volume discounts to automated tiered pricing starts with understanding your current customer distribution. Look at how many customers you have at each volume level, what discounts you're already offering informally, and where the natural breakpoints fall.
Design your tier structure around that data, defaulting to graduated tiers unless you have a specific reason to create volume discount cliffs. Keep the number of tiers manageable, typically three to five, with meaningful discounts that reward growth without destroying your margins.
Implement the tiers in a billing system that supports them natively rather than bolting on workarounds. Test the calculation at boundary conditions, especially at tier edges and with mid-cycle changes.
Finally, communicate the pricing clearly to customers. Show the tier structure, provide calculated examples, and display real-time totals at checkout. Tiered pricing should feel like a reward for volume, not a puzzle to solve.
The spreadsheets tracking your custom deals can finally go away. Your pricing page can tell the truth to every customer, including your biggest ones. And your billing system can handle the math that you've been doing manually, automatically and correctly, for every invoice going forward.
---
### Line Items: Build Any Pricing Model from Composable Parts
Source: https://beta.salable.app/blog/beta-features/line-items-composable-pricing
# Line Items: Build Any Pricing Model from Composable Parts
Most billing systems force you to choose: flat monthly fee or usage-based pricing. Per-seat or metered. Base charge or add-ons. But real products rarely fit these neat categories. Your project management tool might have a monthly platform fee, charge per active user, and bill for storage overages, all in the same subscription. Line items make this natural. Instead of contorting your product to fit your billing system, you compose charges that reflect your actual value delivery.
The limitations of single-model pricing become obvious the moment your product evolves. You launched with a simple per-user fee, but now enterprise customers want a predictable base cost. You added API access, but some customers hammer your endpoints while others barely touch them. Your professional services team offers implementation packages, and billing those separately fragments the customer relationship. Every pricing decision becomes a compromise between what makes business sense and what your billing system can actually do.
Line items dissolve this tension. Instead of picking one pricing model and shoehorning everything into it, you build plans from composable parts. Each part handles one dimension of value: a flat rate for platform access, per-seat charges for team growth, metered billing for variable usage, one-off fees for setup or implementation. Combined, they create pricing that mirrors how customers actually use and value your product.
## The Four Building Blocks
Salable provides four line item types, each designed for a specific kind of charge. Understanding when to use each one lets you construct pricing models that feel intuitive to customers while capturing value accurately.
**Flat-rate line items** charge a fixed amount per billing period regardless of usage. They're the foundation of predictable revenue and work well for platform access, minimum commitments, or bundled features. A \$99/month base fee is a flat-rate line item. So is a \$200/month commitment that includes a certain usage threshold before metered charges kick in.
The stability of flat-rate charges benefits both sides of the transaction. Customers know exactly what they'll pay each month, which simplifies budgeting and reduces billing surprise. You get predictable recurring revenue that doesn't fluctuate with usage patterns. For products where the core value doesn't scale linearly with usage, flat-rate is often the right foundation.
**Per-seat line items** charge based on the number of users, seats, or licenses. The quantity can be fixed at purchase time or dynamic based on active usage. Per-seat pricing aligns revenue with team growth, which makes it popular for collaboration tools, productivity software, and anything where value multiplies as more people use it.
The key decision with per-seat pricing is whether seats are committed or active. Committed seats mean the customer pays for a fixed number whether they use them all or not, providing revenue predictability at the cost of potential customer friction when seats go unused. Active seats mean the customer pays only for seats in use during the billing period, which feels fairer to customers but creates revenue variability and potential gaming.
**Metered line items** charge based on measured consumption, calculated and invoiced at the end of each billing period. API calls, data transfer, storage, compute time, and transactions are classic metered charges. Metered billing captures value from high-usage customers who would otherwise be subsidised by low-usage customers on flat plans.
Metered pricing requires infrastructure to track usage accurately and report it before invoicing. The billing system needs to know that Customer A made 47,293 API calls last month while Customer B made 2,341. This tracking complexity is the cost of usage alignment, but for products with variable consumption patterns, it's often worth it.
**One-off line items** charge a single amount that doesn't recur. Setup fees, implementation packages, training sessions, and initial configuration belong here. They're purchased once, invoiced once, and don't appear on future bills.
One-off charges let you monetise work that happens outside the normal subscription relationship without creating separate invoicing streams. The customer sees one bill that includes their subscription and any one-time charges, keeping the relationship consolidated even when the charges have different characteristics.
## Combining Line Items into Plans
The real power of line items emerges when you combine them. A single plan can include multiple line items of different types, creating pricing models that would be impossible with single-model systems.
Consider a hypothetical analytics platform. The core platform provides dashboards and basic reporting, worth a predictable monthly fee. Team members need access, and each additional analyst increases the value the company extracts. Heavy users query the data warehouse extensively, and those queries cost real infrastructure. New customers need onboarding to get value quickly.
With line items, this translates naturally. The plan includes a \$199/month flat-rate for platform access, \$29/month per analyst seat, \$0.02 per data warehouse query, and a one-time \$500 onboarding fee. A small team with three analysts running moderate queries might pay \$286/month after the initial setup. An enterprise with 50 analysts running millions of queries pays proportionally more, reflecting the greater value they extract.
This composition works because each line item handles its own dimension independently. The flat-rate component is unaffected by seat count. The per-seat charge is unaffected by query volume. Metered billing is unaffected by team size. Each component does one thing well, and combining them creates sophisticated pricing without sophisticated configuration.
## Pricing Model Patterns
Certain combinations of line items appear repeatedly across successful SaaS businesses. Recognising these patterns helps you design pricing that fits your product's value delivery.
**Base plus usage** combines a flat-rate foundation with metered charges for consumption above a threshold. The base fee provides revenue predictability and includes some level of usage, while metered charges capture value from heavy users. This pattern works well when customers have widely varying usage but everyone needs a minimum level of service.
**Platform plus seats** pairs flat-rate platform access with per-seat growth. The platform fee covers infrastructure and capabilities that don't scale with users, while per-seat charges align revenue with the expanding value as more team members adopt the product. Collaboration and productivity tools often follow this pattern.
**Committed seats with overage** sets a minimum seat commitment with metered charges for seats beyond the commitment. The customer commits to paying for 10 seats monthly but can burst to 15 during busy periods with overage charges. This balances revenue predictability with customer flexibility.
**Tiered base with flat add-ons** offers multiple plan levels with different flat-rate bases, then adds capabilities through additional flat-rate line items. The customer picks their tier (Starter, Professional, Enterprise) and optionally adds modules (Analytics, API Access, White Label). Each piece has simple, predictable pricing while the combination creates significant variety.
**Usage-only with minimum** charges purely on consumption but enforces a minimum monthly spend. If actual usage falls below the minimum, the customer pays the minimum. If usage exceeds it, they pay actual usage. This protects your revenue floor while rewarding high-usage customers with pure consumption pricing.
## Designing for Customer Psychology
Pricing isn't just mathematics; it's communication. How you structure line items affects how customers perceive value and make purchasing decisions. A few principles from pricing psychology apply directly to line item design.
**Anchor high, discount down.** When combining line items, customers perceive value based on the total price before any bundled discounts. Showing the individual line item prices and then applying a bundle discount makes the value feel greater than showing the bundled price alone.
**Predictability reduces anxiety.** Customers prefer knowing what they'll pay. If your pricing includes metered components, consider including a usage threshold in the flat-rate portion so customers have a predictable baseline. The metered charges then feel like an option they control rather than an unpredictable cost.
**Simplicity wins at checkout.** While line items let you build complex pricing, the checkout experience should feel simple. Show the total monthly cost prominently, with line item breakdown available for customers who want it. Don't force everyone to parse the composition before buying.
**Separate value from cost.** One-off charges like setup fees can create friction if they feel like arbitrary costs. Frame them as value delivery: "Onboarding package includes dedicated setup session, data migration, and team training." The line item is still a one-off charge, but the messaging emphasises what the customer receives.
## Implementation Considerations
Building line-item-based plans requires coordination between your billing system, your application, and your customer-facing interfaces. Each component has a role to play.
Your billing system needs to understand line item composition natively. Salable handles this by letting you add multiple line items to a single plan, each with its own pricing type, currency, and configuration. The system calculates the combined invoice correctly, handling the interactions between flat, per-seat, metered, and one-off charges.
Your application needs to report usage for metered line items and track seat counts for per-seat charges. This means instrumenting the relevant actions (API calls, storage consumption, active users) and reporting them to the billing system before invoice generation. The accuracy of your billing depends on the accuracy of this reporting.
Your checkout flow needs to communicate the pricing clearly. For plans with multiple line items, show customers what they're getting and what each component costs. If quantities are configurable at checkout (like seat count), update the total dynamically as they adjust. If metered charges apply, explain how usage will be tracked and billed.
Your customer portal needs to show line item breakdown on invoices and subscription details. Customers should be able to see exactly what they're paying for and why. This transparency builds trust and reduces billing-related support requests.
## Evolving Your Pricing Over Time
One advantage of line-item-based pricing is adaptability. As your product evolves, you can add new line items without restructuring everything. Launch a new feature? Add it as an optional line item on existing plans or create a new add-on plan. Discover that a flat-rate component should scale with usage? Convert it to a metered line item.
This flexibility supports pricing experimentation. You can A/B test different line item combinations to see what resonates with customers. You can offer promotional line items that expire after a trial period. You can create customer-specific line items for enterprise deals without building entirely custom plans.
The key is treating line items as modular components that can be mixed, matched, and modified over time. Your initial pricing model doesn't have to be perfect because you have the tools to evolve it as you learn what customers value and what they'll pay for.
## The Shift in Thinking
Moving from single-model pricing to line-item composition requires a mental shift. Instead of asking "which pricing model should we use?" you ask "what are the different dimensions of value we deliver, and how should each be priced?"
Some dimensions are best served by flat charges: predictable, simple, easy to understand. Others align naturally with seat counts: value that scales with team size. Others depend on consumption: value that varies with usage. And some are one-time: value delivered once at the start of the relationship.
Once you see your product through this lens, pricing becomes less about constraints and more about expression. Line items let you say exactly what each component is worth and charge accordingly. The billing system handles the composition, calculation, and invoicing. You focus on delivering value; the pricing follows naturally.
The project management tool with platform fees, per-user charges, and storage billing isn't a complicated edge case anymore. It's just three line items doing what each does best, combined into a plan that reflects reality. And when reality changes, your plan can change with it.
---
### Introducing Salable Beta: Build the Pricing Model Your Business Actually Needs
Source: https://beta.salable.app/blog/announcement/salable-beta-announcement
# Introducing Salable Beta: Build the Pricing Model Your Business Actually Needs
Most billing platforms force you to choose between flat-rate and usage-based billing, and between per-seat and metered billing. Pick one. If your product doesn't fit neatly into a single pricing model, you're left bolting together multiple subscriptions, building custom billing logic, or compromising your charging model. Salable Beta removes that constraint entirely. Today, we're launching a fundamentally different approach to subscription billing—one that lets you combine any charge types you need into a single plan and handle team subscriptions without writing custom code.
## The Problem with "Pick One" Billing
The pricing model that made sense at launch rarely survives contact with real customers. You started with flat-rate monthly subscriptions because they were simple. Then, enterprise customers wanted annual contracts. Then you added a feature that only makes sense to charge by usage. Then, teams needed seat management but also wanted a platform fee.
Traditional billing systems handle each of these individually. They don't handle them together. So you end up managing multiple subscriptions per customer, reconciling charges over different billing cycles, and explaining to confused customers why their invoice has three line items from three different "products" that are really just one product priced three different ways.
The complexity compounds in your codebase. Custom logic to enforce seat limits. Webhook handlers to synchronise subscription states. Edge cases around what happens when someone upgrades one subscription but not the others. Each workaround makes the next change harder.
We built Salable Beta because we kept seeing the same pattern: teams hacking together billing workarounds every time their pricing didn't fit the platform's assumptions, rather than working on features for their core product.
## A Different Architecture
Salable Beta introduces Line Items—composable pricing components that combine within a single plan. Instead of picking one pricing model, you add the charges that reflect how you actually deliver value.
A plan might include a \$99/month platform fee (flat-rate), \$15 per team member (per-seat), \$0.01 per API call (metered), and a \$500 one-time setup charge (one-off). One plan, one subscription, one invoice. Each Line Item appears separately so customers understand exactly what they're paying for.
Each Line Item type does what you'd expect. Flat-rate charges a fixed amount per billing cycle. Per-seat multiplies by team size. Metered tracks consumption and bills at period end. One-off charges are once at the start and never again. Combine them however your pricing demands.
For products with volume discounts, tiered pricing applies graduated or volume-based rates to any Line Item. The first hundred API calls cost one rate, the next thousand cost less, and enterprise volumes cost less still. The calculation happens automatically—no spreadsheets tracking custom deals, no manual invoice adjustments.
Beyond combining charge types within a plan, subscriptions can contain multiple plans altogether. This enables add-on and plugin pricing systems where customers purchase a core product plus optional extras—an analytics module, API access, premium support—all managed as a single subscription with unified billing. Customers build their own bundle from your catalogue; you don't need to anticipate every combination with pre-built packages.
## Per-Seat Billing Made Easy
Per-seat billing sounds simple until you implement it. Who pays versus who uses isn't the same question. A company admin buys fifty seats; individual team members need access. Managing that relationship typically requires custom membership tables, invitation flows, and seat enforcement logic scattered across your application.
Salable Beta's Grantee Groups model this explicitly. The owner holds the billing relationship. Grantees receive access. Groups manage membership. When you check whether someone can access a feature, you're asking a simple question: Does this grantee have this entitlement? Salable handles the rest.
Seat limits are enforced by the group. The subscription allows fifty seats; the group can have at most fifty grantees. No custom enforcement logic required.
## What We're Looking For
This is a beta. The architecture is solid—we've been running it internally and with early partners. But we want to see it in the hands of real developers solving real pricing problems.
We're looking for developers building SaaS products who've felt the limitations of their billing platforms. You've wanted to charge a base fee plus usage, or combine seat-based licensing with metered features, or offer tiered volume discounts without building custom invoicing. If that sounds familiar, we want you to try Salable Beta and tell us what works and what doesn't.
The feedback loop matters. We're actively developing based on what beta users encounter. Issues you report today shape the features we build tomorrow.
## Getting Started
The beta is open now. Sign up at [beta.salable.app](https://beta.salable.app), connect your Stripe account (test mode works fine for experimentation), and start building. The documentation walks through core concepts, pricing configuration, and integration patterns.
If you're migrating from another billing system—or from Salable's previous version—we're here to help. The architecture is different enough that a fresh look at your pricing model is worthwhile. What compromises did you make because your billing system couldn't handle what you actually wanted to charge? Salable Beta might let you undo those compromises.
We're building the billing infrastructure we wished existed when we were building SaaS products ourselves. Today, you can try it. We're keen to see what you build with it.
---
**Further Reading**
- [Salable Quick Start Guide](https://beta.salable.app/docs/quick-start)
- [Core Concepts Guide](https://beta.salable.app/docs/core-concepts)
- [Products & Pricing Guide](https://beta.salable.app/docs/products-and-pricing)
---
### The Hidden Cost of "Simple" Pricing
Source: https://beta.salable.app/blog/insights/the-hidden-cost-of-simple-pricing
# The Hidden Cost of "Simple" Pricing
"Keep it simple" is sound pricing advice—especially when you're starting out. The single $9/month plan has real appeal: no tiers to agonise over, no usage tracking to implement, no decisions for customers to make. It gets you to revenue fast, and there's genuine wisdom in that.
But simple pricing that works at launch rarely stays optimal as you grow. A flat rate that felt fair to your first hundred customers starts leaving money on the table once you're serving enterprises alongside hobbyists. You're either overcharging users who would pay less, or undercharging users who would happily pay more. The question isn't whether to start simple—you probably should. It's knowing when simple stops serving you, and what the minimum complexity looks like that actually captures the value you create.
This distinction matters more than most founders realise. Pricing isn't just a number you slap on your product; it's a signal about who you're for, what you value, and how you think about your relationship with customers. Get it wrong, and you'll spend years wondering why growth feels harder than it should.
## The Seduction of the Single Price
Every founder who launches with a single price point has good reasons. Multiple tiers mean multiple decisions: which features go where, how to name each tier, where to set price points. A single price sidesteps all of that complexity. Ship faster, iterate later.
There's also the fear of overwhelming customers. The conventional wisdom holds that too many choices paralyze buyers—Iyengar and Lepper's famous [jam study](https://psycnet.apa.org/record/2000-16701-012) showed that shoppers bought more jam when offered six varieties than when offered twenty-four. Better to present one clear option and let the product speak for itself.
These arguments feel compelling, but they rest on a flawed premise: that all your customers are essentially the same. They're not. The solo developer evaluating your tool for a weekend project values it differently than the enterprise team planning to deploy it across hundreds of engineers. The small agency using your product for one client has different needs than the consultancy building their entire practice around it.
A single price forces these wildly different customers into the same box. The solo developer looks at your $99/month price and thinks "I'd pay $20 for this, but not $99." The enterprise team looks at the same price and thinks "This seems suspiciously cheap—is it really enterprise-ready?" You've priced yourself out of both conversations.
## The Math of Misaligned Pricing
Let's make this concrete. Imagine your market has 1,000 potential customers, and you've chosen a single price of $49/month. The upper limit on your monthly revenue is $49,000. Seems fine.
But now imagine those customers actually fall into three natural segments based on how much value they derive from your product. Three hundred are hobbyists who would pay up to $19. Four hundred are professionals who would happily pay $49—you priced this segment perfectly. Three hundred are teams who would pay $149 because your product drives their daily workflow.
With a single $49 price, those 300 hobbyists never convert. They wanted your product, but not at that price. That's 300 customers you never see, but the cost is invisible—you don't know they exist. Meanwhile, the 300 teams are getting a steal. They'd pay three times more, but you never asked.
Run the alternative math. With three tiers priced at $19, $49, and $149, you capture all three segments. That's $5,700 from hobbyists, $19,600 from professionals, and $44,700 from teams—a total of $70,000. The "simple" single price left $35,700 per month on the table. Over a year, that's $428,400 in revenue you never captured, not through bad execution, but through a pricing structure that couldn't accommodate the value you were actually creating.
This is a simplified example, but it demonstrates the logic you'll need to grapple with: a single price point can only optimise for one customer segment, leaving value uncaptured at both ends—customers who would pay more and customers who won't convert at all.
## Simplicity Versus Clarity: The Crucial Distinction
The argument for simple pricing conflates two different concepts: simplicity and clarity. A single price is simple. But a well-designed three-tier structure can be clearer—it tells customers more about who the product is for and how to choose.
Consider how this works in practice. A solo consultant lands on your pricing page and sees three options: Starter at $19 for individuals, Professional at $49 for small teams, and Enterprise at $149 for organisations. Within seconds, they know exactly which tier is for them. The tier names and descriptions do the work of qualification that a single price cannot.
Contrast this with a single $49 price. The consultant wonders: Is this designed for someone like me, or am I paying for features meant for larger teams? Am I getting good value, or am I subsidizing enterprise functionality I'll never use? The simplicity creates confusion about fit, even as it eliminates choice.
Clarity comes from alignment between pricing and customer segments, not from reducing options to one. Three tiers that map to distinct use cases are cognitively easier than a single price that raises questions about who it's meant for.
## Finding the Right Level of Sophistication
If simplicity isn't the goal, what is? The answer is the minimum pricing complexity that captures the value you create. This varies by product and market, but a framework can help you find the right level.
Start by identifying natural customer segments. These aren't arbitrary divisions you impose; they're groups that already exist in your market with meaningfully different needs and willingness to pay. A project management tool might serve solo freelancers, small teams, and large organisations. Each segment uses the product differently and derives different value from it.
Next, identify the value metric—the unit that scales with the value customers receive. For some products, this is users or seats. For others, it's usage volume, projects, or storage. The right value metric passes two tests: customers intuitively understand why they should pay more as this metric increases, and it correlates with the value they're actually getting.
Finally, build tiers around the intersection of segments and value metrics. Each tier should have a clear target customer and a price that reflects what that customer would reasonably pay. The tiers should be distinct enough that customers can easily self-select, but not so numerous that the choice becomes overwhelming.
Three tiers work remarkably well for most SaaS products. It's enough to capture meaningfully different customer segments while remaining easy to understand. Four or five tiers can work if you genuinely serve distinct segments, but beyond that, you're likely overcomplicating without capturing additional value.
## The Cost of Delaying Pricing Evolution
Perhaps the most hidden cost of simple pricing is the opportunity cost of learning. Pricing isn't something you set once and forget; it's a lever you should continuously optimise. But a single price gives you almost no data to learn from.
With multiple tiers, you can observe which customer segments expand fastest, which have the highest conversion rates, and where buyers naturally cluster. When your Professional tier converts at twice the rate of Enterprise, you know the Enterprise tier needs repositioning. When customers frequently upgrade from Starter to Professional after three months, you can adjust the Starter feature set to accelerate that journey.
None of this learning happens with a single price. You know your conversion rate and your churn rate, but you don't know why customers convert or churn, or which segments you're serving well versus poorly. What started as a feature—simplicity—becomes a blindfold.
This compounds over time. The company with tiered pricing iterates based on data, gradually optimizing toward the pricing structure that best fits their market. The company with simple pricing operates on intuition, making large, infrequent changes because they lack the feedback loops to make small, continuous improvements.
Patrick Campbell, founder of ProfitWell, puts it bluntly: "The company that iterates on pricing fastest wins." But you can't iterate on what you can't measure, and simple pricing measures almost nothing.
## When Simple Pricing Actually Works
All this said, there are contexts where a single price genuinely makes sense. Early-stage products without clear customer segments benefit from starting simple. If you don't yet know who your best customers are or how they derive value, tiered pricing would just be guessing. Better to start with one price, learn from early adopters, and add tiers once you understand the market.
Commodity products with undifferentiated value can also justify simple pricing. If every customer gets essentially the same value regardless of how they use the product, there's nothing for tiers to capture. But true commodities are rare in SaaS—most products create different value for different users.
Bottom-up products that rely on viral adoption sometimes benefit from simple pricing that removes all friction. Slack started with a radically simple model: free for small teams, paid when you needed history and integrations. This wasn't unsophisticated—it was precisely calibrated to their bottom-up growth motion. The simplicity was strategic, not default.
The key is intentionality. Simple pricing as a deliberate choice based on your growth model is different from simple pricing as a way to avoid hard decisions about customer segments and value metrics.
## The Path to Pricing Clarity
If your current pricing is leaving money on the table, how do you evolve toward something better? The transition matters as much as the destination.
Start with customer research, not competitor analysis. Talk to customers in each segment about how they use your product and what outcomes they care about. Ask about value, not price—"What would it cost you if this product disappeared?" reveals more than "How much would you pay?" The goal is understanding how different segments derive different value, so you can differentiate tiers accordingly.
Design tiers around outcomes, not features. Instead of defining the Professional tier by "unlimited projects and 10GB storage," define it by who it serves: "For teams shipping multiple projects who need collaboration features." Then work backward to the features that enable those outcomes. This approach produces tiers that customers can self-select into because they recognise themselves in the description.
Communicate the change thoughtfully. Existing customers on a single price will wonder how they're affected. Be transparent about what's changing and why, and consider grandfathering existing customers at their current rate. The goal is capturing more value from new customers, not extracting more from existing ones.
Finally, commit to ongoing iteration. Your first tiered pricing won't be optimal, and that's fine. The point is building the infrastructure for learning—the tiers, the analytics, the experimentation mindset. The specific prices and features will evolve as you learn.
## Minimum Viable Sophistication
The goal isn't pricing simplicity—it's pricing clarity. A three-tier structure that maps to real customer segments is clearer than a single price that fits no one well. It tells customers who the product is for, helps them self-select into the right tier, and gives you data to continuously improve.
The hidden cost of simple pricing isn't the complexity you avoided; it's the revenue you never captured, the customers who never converted, and the learning that never happened. These costs are invisible, which makes them easy to ignore—but they compound over time.
Find the minimum complexity that captures the value you create. Don't default to simplicity because sophistication feels risky. The real risk is leaving growth on the table.
---
### Your First Subscription Product: From Zero to Revenue in an Afternoon
Source: https://beta.salable.app/blog/saas-startup-guides/your-first-subscription-product
# Your First Subscription Product: From Zero to Revenue in an Afternoon
You've built something people want to pay for. Maybe it's a SaaS tool that's been running free while you validated the concept, or perhaps you're starting fresh with a clear monetisation strategy. Either way, you're facing the same question every developer confronts: how do you actually charge people?
The billing landscape is littered with engineers who spent months building custom systems and are now stuck maintaining billing code instead of shipping product features. There's a better path, and after reading this you'll be on it.
## The Overthinking Trap
Most developers approach billing like they approach feature development: they map out every edge case, design for scale they don't have, and build flexibility they'll never use. That instinct serves them well when architecting application code, but it's counterproductive for billing.
Consider what typically happens. A developer sits down to implement subscriptions and immediately starts listing requirements. They need to handle monthly and annual billing. They need to support multiple tiers. They need upgrade and downgrade paths. They need proration logic. They need to handle failed payments gracefully. They need webhooks to keep their database in sync. Before writing a line of code, they've designed a system complex enough to require weeks of implementation.
Meanwhile, their product sits there, free, while potential revenue walks out the door.
Here's the insight that changes everything: you don't need any of that complexity on day one. You need a single plan, a checkout flow, and a way to know who's paid. Everything else can wait until customers ask for it.
## The Minimum Viable Billing Stack
A working subscription product requires exactly two things: a way to take money and a way to block users who haven't given you any. That's it.
For taking money, you need one product to sell. "Pro Plan: $29/month" is enough. You don't need a free tier or multiple pricing options on day one. Customers who want to pay will pay; customers who want options will tell you what options they want after you've launched.
For blocking non-paying users, you need an entitlement check—code that answers one question: does this user have access to this feature? On day one, this can be as simple as verifying whether someone has an active subscription. It doesn't need to be sophisticated; it needs to exist.
Salable gives you both.
## Building the Happy Path First
Your first implementation should handle exactly one scenario: a new customer signs up, pays, and gains access to your product. That's the happy path, and it's the only path that generates revenue.
Start by creating your product in your billing system. Give it a name that makes sense to customers, set a price that feels right (you can always change it later), and configure a monthly billing interval. Don't agonise over the price. Pick a number, launch, and let the market tell you if you're wrong.
Next, set up the checkout flow. A customer clicks "Subscribe," completes checkout, and returns to your application as a paying customer.
Finally, implement the entitlement check. When a user tries to access a paid feature, your code should verify they have an active subscription. If they do, let them through. If they don't, show them the paywall.
The entire implementation can be completed in an afternoon. Not because you're cutting corners, but because you're deferring complexity until it's necessary.
## What You're Deliberately Ignoring (For Now)
This approach works because it's honest about what matters on day one versus what can wait. You're deliberately setting aside several things that feel important but aren't yet.
Multiple tiers can wait. Yes, conventional wisdom says you need a Good/Better/Best pricing page. But that wisdom assumes you know which features belong in which tier. You don't. You're guessing. Launch with one tier, watch how customers use your product, and add tiers when you understand the natural value segments.
Annual billing can wait. Annual plans improve cash flow and reduce churn, but they also complicate refunds, proration, and plan changes. More importantly, you don't yet know if customers will stick around for a year. Prove monthly retention before optimising for annual commitment.
Free trials can wait. Trials are powerful conversion tools, but they're also a form of delayed revenue and a source of complexity. Trial users need nurturing, trial-to-paid conversion needs tracking, and trial abuse needs preventing. Launch with immediate payment and add trials once you understand your conversion funnel.
Usage-based pricing can wait. Metered billing aligns your revenue with customer value, but it requires infrastructure: usage tracking, billing calculations, and customer-facing dashboards. Start with flat-rate pricing until you have usage data that justifies the complexity.
None of these features are hard to add later. They're just unnecessary now. Every feature you defer is engineering time you can spend on your actual product.
## The Launch Checklist
Before you announce your paid plan, verify that the critical path works end-to-end. Create a test account using your payment processor's test mode. Walk through the checkout flow as a customer would. Confirm that completing payment creates the right records in your system. Verify that your entitlement check correctly identifies paid users. Test that paid features actually unlock.
This isn't exhaustive testing; it's smoke testing the one flow that matters. If a new customer can sign up and access paid features, you're ready to launch. If something fails, fix it before moving on.
You'll also want a way to handle the edge cases that will inevitably arise. What happens if someone emails saying they paid but can't access the product? You need a way to manually check their subscription status and, if necessary, grant access while you investigate. This doesn't need to be a polished admin interface. It just needs to be possible.
## Your First Customers Aren't Your Last Customers
The objection to launching simple is always some variation of "but what about professional customers who need enterprise features?" The answer is that professional customers aren't buying your product today. Early adopters are.
Early adopters are tolerant of rough edges and missing features because they're buying potential, not polish. They'll tell you what features matter through support tickets and feature requests. They'll teach you what pricing models make sense for your market. They'll surface the edge cases you couldn't have anticipated.
Your job on day one is to capture this learning by having something to sell. Every week you spend building features no one asked for is a week of customer feedback you didn't collect.
This doesn't mean you should ship broken software or ignore obvious problems. It means your definition of "ready to launch" should be "can I charge someone for this?" rather than "have I anticipated every possible scenario?"
## Growing Beyond Day One
Once you have paying customers, the roadmap becomes clearer. Usage data reveals which features drive value, and those insights shape how you structure tiers. Customer feedback points you toward the pricing models that fit your market. Support tickets highlight which edge cases need automation.
The pattern is consistent: launch simple, observe, and expand based on evidence. Add a second tier when customers ask for different feature sets. Add annual billing when monthly retention proves strong. Add usage-based components when flat-rate pricing leaves money on the table.
This iterative approach isn't just faster for initial launch; it's more likely to produce pricing that works. Startups that launch with complex pricing models based on intuition usually end up simplifying. Startups that launch simple and expand based on evidence usually get it right.
## The Afternoon That Changes Everything
Here's what's possible in a couple of hours: define your product in Salable, configure a checkout flow that handles payment collection, implement an entitlement check that gates access to paid features, and test the end-to-end flow to verify everything works.
By the end of the afternoon, you'll have something that seemed complicated this morning: a way to charge money for your work. Not a theoretical system design. Not a roadmap for future billing infrastructure. A real product that real people can pay for, today.
The revenue might be modest at first. Your first customer might be someone you know. Your second customer might take a week to find. But you'll have crossed the threshold from "building something" to "running a business." And everything that follows—better pricing, more features, larger customers—builds on that foundation.
The complexity can come later. Today, just get paid.
---
_Building your first subscription product? [Salable's Quick Start Guide](https://beta.salable.app/docs/quick-start) walks you through the complete setup in under an hour, from product creation through your first checkout._