SaaS Billing - The Complete Guide (2026)

SaaS billing is the system that turns a subscription or a unit of usage into money. It stores plans, creates and tracks subscriptions, meters usage, generates invoices, processes payments through a provider like Stripe, and reacts to webhook events such as successful charges and refunds. The hard part is rarely charging a card; it's handling webhooks idempotently, metering usage accurately, and recovering failed payments without losing customers.
What SaaS billing actually is
SaaS billing is the end-to-end system that manages plans, subscriptions, usage, invoices, and payments for a software product. It sits between your product and a payment provider, translating "this customer is on the Pro plan and used 40,000 API calls" into an invoice, a charge, and an updated entitlement in your database.
If you're building a SaaS product from scratch, the surprise is how little of this is about payment processing. Stripe, Lemon Squeezy, and similar providers already handle card storage, PCI compliance, and the actual movement of money. Billing is what you build around that: plans, subscription state, usage counters, and reliable event handling.
Pro tip: Treat billing state (plan, subscription status, entitlements) as data you own in your own database, not something you look up from the payment provider on every request. Providers send webhooks to keep your copy in sync; your app should never block on a live API call to Stripe to answer "can this user do X?"
The building blocks of a SaaS billing system
Every SaaS billing setup, hand-rolled or bought, needs the same core pieces. Here's what each one does and how FastStaq implements it.
Component | What it does | FastStaq's implementation |
|---|---|---|
Plans | Defines pricing tiers, features, and limits |
|
Subscriptions | Tracks a customer's active plan, status, and renewal |
|
Checkout | Collects payment and starts a subscription or one-time purchase | Stripe Checkout by default; Lemon Squeezy via a |
Customer portal | Lets customers update cards, cancel, or view invoices without a support ticket | Provider-hosted portal (Stripe Customer Portal) wired into the app |
Entitlements | Maps a plan or purchase to what a user can actually do |
|
Usage metering | Counts consumption for usage-based or credit-based pricing |
|
Invoices | Records what was charged and when | Generated by the payment provider, referenced by webhook events |
Webhook handling | Reacts to provider events (payment succeeded, subscription canceled) | Idempotent handlers backed by |
Dunning | Recovers failed payments through retries and customer notices | Provider-side automatic retries plus queued email reminders |
Note: The FastStaq column describes what ships in the source you get for $299 one-time. It's a pluggable integration with Stripe and Lemon Squeezy, not a replacement for either provider.
Pricing models: flat, tiered, usage-based, and hybrid
Most SaaS products start with a flat or tiered subscription because it's the simplest model to reason about: one price, one set of features, billed monthly or annually. Usage-based pricing (per API call, per seat-month, per GB) fits products where cost scales with consumption, but it adds real complexity: you now need to meter usage accurately before you can bill for it.
Hybrid models, a base subscription plus usage overages, are increasingly common for developer tools and AI products. They give you predictable base revenue and let you capture upside from heavy users.
Picking a model is its own decision with its own tradeoffs around predictability, sales complexity, and metering cost. Our pricing models guide covers flat, tiered, usage-based, and hybrid pricing in more depth.
Subscriptions vs. usage-based billing
Subscription billing charges a fixed amount on a recurring schedule (monthly, annually) regardless of how much the customer uses the product. It's predictable for both sides and simple to implement: create a Subscription record, let the provider handle renewal, react to invoice.paid and invoice.payment_failed webhooks.
Usage-based billing charges based on consumption: API calls, compute minutes, storage, seats. It's harder to build because you need an accurate, race-condition-free way to count usage before you generate an invoice. Stripe's meters and FastStaq's CreditBalance / CreditTransaction models both exist to solve this: track a running balance, debit it on usage, and reconcile against the provider periodically.
For most new SaaS products, subscription billing is the better place to start. Usage-based pricing becomes worth the additional metering and billing complexity once you have paying customers whose usage varies enough that a flat price consistently overcharges some customers and undercharges others.
Webhooks and idempotency: the part that actually breaks
Here's what most first-time builders miss before shipping a SaaS billing integration: your payment provider will send you the same webhook event more than once. Network retries, timeouts, and provider-side infrastructure hiccups all cause duplicate deliveries. Stripe documents this behavior directly for its own webhook endpoints.
If your handler isn't idempotent, a duplicate invoice.paid event can credit a user's account twice, send two welcome emails, or double-grant a one-time purchase. The fix is straightforward in concept: record every event ID before you act on it, and skip anything you've already seen.
Here's an illustrative pattern for an idempotent webhook handler (untested here, adapt it to your framework and error handling):
typescript
// server/src/webhooks/stripe-handler.ts (illustrative, untested)
import { prisma } from '../db';
import Stripe from 'stripe';
export async function handleStripeWebhook(event: Stripe.Event) {
// Claim the event first. The unique constraint on stripeId
// does the actual dedup work, even under concurrent retries.
try {
await prisma.stripeEvent.create({
data: { stripeId: event.id, type: event.type, status: 'processing' },
});
} catch (err) {
console.log(`Skipping duplicate event: ${event.id}`);
return;
}
switch (event.type) {
case 'invoice.paid':
await handleInvoicePaid(event);
break;
case 'customer.subscription.deleted':
await handleSubscriptionCanceled(event);
break;
// ...other event types
}
await prisma.stripeEvent.update({
where: { stripeId: event.id },
data: { status: 'completed' },
});
}This is the shape of what FastStaq's StripeEvent and LemonSqueezyEvent ledgers do: claim the event ID with a unique constraint, process it, then mark it complete. It's a small pattern, but skipping it is how double-charges and duplicate emails happen in production.
Further reading: Stripe's webhook documentation covers duplicate delivery and idempotent processing directly: Receive Stripe events in your webhook endpoint.
Failed payments and dunning
Cards expire, banks decline charges, and payment methods fail more often than you'd expect at real scale. Dunning is the process of recovering that revenue: retrying the charge on a schedule and notifying the customer before you cut off access.
Stripe's Billing product includes automatic collection features that retry failed invoices on a schedule and can notify customers about failed payments. You don't have to build the retry logic yourself, but you do need to react to invoice.payment_failed and customer.subscription.past_due events to update your own app state (lock a feature, show a banner, queue a reminder email).
Skip building custom dunning logic if you're pre-revenue or early stage; provider-side retries plus a webhook-driven status update cover most cases. Build custom recovery flows (in-app banners, sequenced emails, grace periods) once failed payments are costing you retained revenue you can actually measure.
How FastStaq handles billing
FastStaq doesn't replace Stripe or Lemon Squeezy. It wires one of them into a working app so you're not building the plumbing from a blank repo.
Provider choice: Stripe is the default gateway. Set
PAYMENT_PROVIDERin your environment to switch to Lemon Squeezy instead. Both go through the same shared provisioning path (provisionOneTimeGrant.ts), so a one-time purchase grants access the same way regardless of which provider processed the charge.Webhook safety: Every incoming event from Stripe or Lemon Squeezy is written to a
StripeEventorLemonSqueezyEventledger before it's processed, so duplicate deliveries are a no-op instead of a duplicate charge or a double-sent email.Data model:
Plan,Subscription, andPackageEntitlementmodels, part of the 76 Prisma models FastStaq ships, hold plan definitions, subscription state, and what each entitlement unlocks.CreditBalanceandCreditTransactionmodels are included in source for products that need usage or credit-based metering on top of subscriptions.Where it lives: All of this runs on Postgres via Prisma, so you can host it on Supabase's managed Postgres, Neon, or your own instance by setting
DATABASE_URL. Background jobs like payment retries and receipt emails run through Redis and BullMQ workers instead of blocking a request.
FAQ
What is SaaS billing? SaaS billing is the system that manages plans, subscriptions, usage, invoices, and payments for a software product. It covers everything between "a customer wants to pay" and "your app knows what they're allowed to do," including reacting to events from a payment provider like Stripe.
How do you build SaaS billing for a new product? Start with a payment provider (Stripe or Lemon Squeezy are common choices), define your plans, and build a Subscription model that syncs from provider webhooks. Add idempotent webhook handling early, since duplicate events are a normal part of how providers deliver them, not an edge case you can defer.
Subscription billing vs. usage-based billing: which should you pick? Subscription billing is simpler to build and predictable for customers; pick it first unless your costs scale directly with usage. Usage-based billing fits products like APIs or compute tools where a flat price either overcharges light users or undercharges heavy ones, but it requires accurate usage metering before you can charge for it.
Do you need Stripe to build SaaS billing? No. Stripe is the most common choice and has extensive documentation, but Lemon Squeezy and other providers offer similar checkout, subscription, and webhook primitives. FastStaq, for example, defaults to Stripe and supports switching to Lemon Squeezy through a single environment variable.
Is FastStaq billing software? No. FastStaq is a SaaS boilerplate that wires Stripe or Lemon Squeezy into a working app, including checkout, customer portal, webhook idempotency, and entitlements. It integrates a payment provider; it doesn't replace one.
Where to go next
SaaS billing comes down to a handful of building blocks: plans, subscriptions, usage metering, invoices, and webhook events, wired to a payment provider and made safe against duplicate deliveries. Get the webhook idempotency and entitlement logic right early; it's expensive to retrofit once customers depend on it.
If you are building this from scratch, start with a practical Stripe and Next.js integration. Once the core payment flow is working, add usage-based billing or subscription billing based on your pricing model.
If you'd rather not build the plumbing, FastStaq ships this billing system with Stripe by default as part of the $299 one-time source code. See our features page for the full picture of what's included.


