SaaS Billing Systems Guide
A production SaaS billing system has six components: Stripe Checkout (payment collection), Stripe Subscriptions (recurring billing), Webhook handlers (database sync), Billing Portal (self-service), Dunning sequences (revenue recovery), and Feature gating (plan enforcement). Webhooks — not the checkout redirect — are the authoritative source of subscription state.
Billing is non-negotiable in a SaaS MVP. A product without billing is a demo. 4Byte Agency integrates the full billing stack in week 3 of every 28-day build.
▸ Billing Components
Six components of a complete SaaS billing system
Billing is not a single integration — it's six interconnected components. Missing any one of them creates revenue leakage, customer experience failures, or security vulnerabilities.
Payment Collection
The interface where customers enter payment details. Stripe Checkout is the fastest, most secure implementation — a Stripe-hosted page that handles card validation, 3D Secure, and PCI compliance automatically.
Tools
Subscription Management
The engine that manages recurring billing — creating subscriptions, handling trial periods, processing plan upgrades and downgrades, and scheduling cancellations. Stripe handles the recurring charge logic; your code handles the business rules.
Tools
Webhook Event Handler
The critical sync layer between Stripe and your database. Every subscription state change — payment success, failure, cancellation, upgrade — is delivered as a webhook event. Your handler must process these events and update your database subscription record accordingly.
Tools
Self-Service Billing Portal
A Stripe-hosted portal where customers manage their own subscription — update payment methods, download invoices, change plans, and cancel. Reduces billing support requests to near zero and is implemented in one API call.
Tools
Dunning & Revenue Recovery
The automated process of recovering failed payments. When a card charge fails, dunning sends a structured email sequence asking customers to update their payment method, while Stripe retries the charge on a schedule. Recovers 10–20% of at-risk MRR.
Tools
Feature Gating by Plan
The mechanism that enforces plan-level access to features. Subscription status and plan tier are checked at the API middleware layer before executing gated operations. Free, trial, and paid users see different feature sets based on their active subscription.
Tools
▸ Pricing Models
Four SaaS pricing models — how to choose the right one
Pricing model choice directly impacts revenue ceiling, churn behaviour, and billing complexity. Here are the four models with honest trade-offs and clear guidance on which to use at which stage.
Flat-Rate (Fixed Price)
$49/month — unlimited users, unlimited usage
One price for full access to the product. No per-seat or usage variables. Easiest to understand, easiest to sell, easiest to build.
Advantages
- Simplest for customers to understand
- No billing complexity by user count
- Easiest to implement and test
- Predictable revenue per customer
Trade-offs
- Leaves money on the table from power users
- Hard to segment by company size
- No natural expansion revenue path
Revenue Model
Fixed MRR per customer. Growth comes from new customers only.
Best For
Single-user tools, early-stage SaaS, products with high variance in user count
Per-Seat (Per-User)
$15/seat/month — first 5 seats minimum
Price scales with the number of user seats in the organisation. Each additional team member added increases the monthly bill. Natural expansion revenue as teams grow.
Advantages
- Revenue scales naturally with team growth
- Easy to segment by company size
- Built-in expansion revenue mechanism
- Simple to communicate value
Trade-offs
- Customers resist adding seats (cost avoidance)
- Shared logins become a common workaround
- Revenue predictability harder at small team sizes
Revenue Model
MRR grows as customers add seats. Best expansion metric: seats per org over time.
Best For
Team collaboration tools, project management, CRM, HR software
Usage-Based (Metered)
$0.01 per API call, first 1,000 free
Price scales with actual product usage — API calls, messages sent, documents processed, AI tokens consumed. Customers pay only for what they use. Revenue scales perfectly with value delivered.
Advantages
- Perfect value alignment
- Low friction to start (low initial cost)
- Revenue scales with customer success
- No seat-sharing workarounds
Trade-offs
- Unpredictable revenue per customer
- Higher billing infrastructure complexity
- Customers may limit usage to control costs
- Hard to budget from customer side
Revenue Model
Revenue directly tied to customer usage volume. Forecasting requires usage trend data.
Best For
API products, AI-powered tools, email sending platforms, infrastructure SaaS
Tiered Plans
Starter $29/mo · Pro $79/mo · Business $199/mo
Multiple fixed-price plans (Starter, Pro, Business, Enterprise) with different feature sets and usage limits. The most common SaaS pricing structure. Allows natural upsell from entry to higher tiers.
Advantages
- Natural upgrade path built in
- Segment customers by value and use case
- Flexibility to gate premium features
- Familiar pattern customers understand
Trade-offs
- Plan design requires careful thought
- Feature gating logic adds development complexity
- Risk of underpricing tiers vs actual value
Revenue Model
Expansion revenue from plan upgrades. Goal: move customers from Starter to Pro and Pro to Business.
Best For
Most B2B SaaS products with a clear feature differentiation between customer segments
▸ Stripe Integration
How to integrate Stripe in a SaaS — step by step with real code
The exact six-step Stripe integration 4Byte Agency implements in every SaaS product. Each step has a defined output and a code pattern — not pseudocode, but production-ready implementation guidance.
Configure Stripe Products and Prices
Create Products and Prices in the Stripe Dashboard (or via API). Each pricing plan maps to a Stripe Price object with its interval (monthly/annual) and amount. Store the Stripe Price ID in your codebase constants.
Code Pattern
// .env STRIPE_SECRET_KEY=sk_live_... STRIPE_WEBHOOK_SECRET=whsec_... NEXT_PUBLIC_STRIPE_STARTER_PRICE_ID=price_... NEXT_PUBLIC_STRIPE_PRO_PRICE_ID=price_...
Create Stripe Customer on Organisation Creation
When a new organisation is created, immediately create a corresponding Stripe Customer object and store the stripe_customer_id on the organisation record. This links your tenant to Stripe and enables future billing operations.
Code Pattern
const customer = await stripe.customers.create({
email: user.email,
name: organisation.name,
metadata: { organisation_id: org.id },
});
await db.organisations.update({
where: { id: org.id },
data: { stripe_customer_id: customer.id },
});Create Checkout Session for Upgrade
When a user clicks 'Upgrade', create a Stripe Checkout Session and redirect them to the Stripe-hosted payment page. Pass success_url and cancel_url to handle both outcomes. Checkout handles card collection, 3D Secure, and PCI compliance.
Code Pattern
const session = await stripe.checkout.sessions.create({
customer: org.stripe_customer_id,
mode: 'subscription',
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${origin}/dashboard?upgraded=true`,
cancel_url: `${origin}/pricing`,
subscription_data: { trial_period_days: 14 },
});Handle Webhooks — The Source of Truth
Never trust the redirect URL to confirm payment. Always use webhooks as the authoritative source of subscription state. Verify Stripe-Signature on every incoming webhook. Process events idempotently — the same event may arrive multiple times.
Code Pattern
const sig = headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(
rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET
);
// Process based on event.type
switch (event.type) {
case 'invoice.payment_succeeded': ...
case 'customer.subscription.deleted': ...
}Create Billing Portal Session for Self-Service
For the billing settings page, create a Stripe Billing Portal Session and redirect the customer. The portal handles all self-service billing operations — no custom UI needed. Configure allowed actions (update payment, cancel, change plan) in the Stripe Dashboard.
Code Pattern
const portalSession = await stripe.billingPortal.sessions.create({
customer: org.stripe_customer_id,
return_url: `${origin}/settings/billing`,
});
return redirect(portalSession.url);Gate Features by Subscription Status
In API middleware and UI components, check subscription status and plan tier before granting access to paid features. The database subscription record (synced from Stripe via webhooks) is the source of truth — never call the Stripe API inline for every request.
Code Pattern
// API middleware
const subscription = await db.subscriptions.findFirst({
where: { organisation_id: org.id, status: 'active' }
});
if (!subscription) {
return json({ error: 'UPGRADE_REQUIRED' }, { status: 402 });
}▸ Subscription Lifecycle
Five subscription states — what triggers each and how to handle it
Every subscription moves through these five states. Each state has defined webhook triggers, database actions, and next-state transitions. Missing any one creates silent billing or access failures.
Trial
Customer signs up and starts a free trial period. Full product access, no charge yet. Trial period typically 7–14 days.
Webhook Events
customer.subscription.created (status: trialing)Database Action
Set subscription.status = 'trialing'. Grant full feature access. Start trial countdown.
Next State
Active (trial converts) or Incomplete (card declined) or Cancelled (explicit cancel)
Active
Customer is paying successfully. Subscription renews automatically at the period end. Full feature access granted based on plan tier.
Webhook Events
customer.subscription.updated (status: active)invoice.payment_succeededDatabase Action
Set subscription.status = 'active'. Update current_period_end. Grant plan features.
Next State
Past Due (payment fails) or Cancelled (customer cancels) or Updated (plan change)
Past Due
A payment attempt failed. Stripe is retrying on a schedule. Customer still has access but is in the dunning sequence. This is a critical revenue recovery window.
Webhook Events
invoice.payment_failedcustomer.subscription.updated (status: past_due)Database Action
Set subscription.status = 'past_due'. Trigger dunning email sequence. Maintain feature access during retry window.
Next State
Active (payment recovered) or Cancelled (all retries exhausted)
Cancelled
Subscription has been cancelled — either by the customer, by Stripe after exhausted retries, or by admin action. Access is revoked at period end for voluntary cancellations, immediately for payment failures.
Webhook Events
customer.subscription.deletedcustomer.subscription.updated (cancel_at_period_end: true)Database Action
Set subscription.status = 'cancelled'. Set cancel_at_period_end. Revoke feature access at period_end or immediately.
Next State
Can resubscribe — treat as new subscription creation
Paused
Customer pauses their subscription instead of cancelling. Billing is suspended for a defined period. Feature access is restricted. An effective churn prevention tool for seasonal or budget-constrained customers.
Webhook Events
customer.subscription.updated (pause_collection: { behavior: 'void' })Database Action
Set subscription.status = 'paused'. Restrict to free-tier features. Show resume prompt.
Next State
Active (resumes) or Cancelled (doesn't resume in time)
▸ Webhook Events Reference
The eight Stripe webhook events every SaaS must handle
These eight events cover 99% of subscription state changes in a SaaS product. Each one has a defined trigger, required database action, and priority level.
customer.subscription.createdinvoice.payment_succeededinvoice.payment_failedcustomer.subscription.updatedcustomer.subscription.deletedcustomer.subscription.trial_will_endpayment_method.automatically_updatedinvoice.upcomingIdempotency is mandatory: Stripe retries webhook delivery for up to 3 days when your endpoint returns a non-2xx response or times out. Every handler must be safe to run multiple times with the same event. Use upsert operations and store processed Stripe event IDs to detect and skip duplicate deliveries.
▸ Dunning & Revenue Recovery
The 14-day dunning sequence that recovers 15% of at-risk MRR
Dunning is the most underbuilt part of most SaaS billing systems. A three-email sequence combined with Stripe Smart Retries recovers 10–20% of payments that would otherwise churn. Here's the exact sequence 4Byte builds.
Trigger
Payment fails
System Action
Stripe Smart Retry (immediate retry for some failure types). Set status = past_due. Send Dunning Email 1.
Subject: Action required: Your payment didn't go through
Payment failed notice. Clear link to update payment method in Stripe Billing Portal. Current period end date shown.
Trigger
No payment update after 3 days
System Action
Stripe retries charge. Send Dunning Email 2 if still failing.
Subject: Your account will be restricted in 4 days
Urgency increase. Feature access warning. Direct link to update card. Offer support contact.
Trigger
Still no payment update
System Action
Stripe final retry. Send Dunning Email 3 with final warning.
Subject: Final notice: Update your payment to keep access
Final warning. Specific date access will be revoked. One-click update CTA. Pause option offered as alternative to cancel.
Trigger
All Stripe retries exhausted
System Action
Stripe fires customer.subscription.deleted. Set status = cancelled. Revoke feature access. Send Cancellation Email.
Subject: Your account has been suspended
Access revoked notice. Grace period offer (48hr to update and restore). Resubscribe link. Support contact.
Trigger
Post-cancellation win-back
System Action
Send win-back email to cancelled customers who haven't resubscribed.
Subject: We're holding your data — come back and pick up where you left off
Data retention assurance. Reactivation link. Optionally offer a discount or extended trial.
▸ Billing Metrics
Six billing metrics every SaaS founder must track
These six metrics tell you whether your SaaS billing system is generating healthy, growing, recoverable revenue — or silently leaking at each stage of the subscription lifecycle.
Monthly Recurring Revenue (MRR)
Sum of all active subscription values normalised to a monthly figure. The primary health metric for SaaS billing.
Formula
MRR = Σ (active subscriptions × monthly plan value)Target
Growing 10–20% month-over-month in early stage
Churn Rate
Percentage of MRR or customers lost in a period. The most important retention signal in SaaS.
Formula
Monthly Churn = (MRR lost / MRR at start of month) × 100Target
< 3% monthly for early-stage B2B SaaS
Net Revenue Retention (NRR)
Revenue from existing customers after expansion, contraction, and churn. Above 100% means existing customers are generating more revenue over time.
Formula
NRR = (Starting MRR + Expansion - Contraction - Churn) / Starting MRR × 100Target
> 100% is healthy. > 120% is excellent
Trial-to-Paid Conversion Rate
Percentage of trial users who convert to a paid subscription at trial end.
Formula
Conversion = (Trial → Paid) / Total Trials × 100Target
> 15% for card-required trials. > 5% for no-card trials
Payment Recovery Rate
Percentage of failed payment invoices that are eventually recovered through retries and dunning.
Formula
Recovery Rate = (Recovered invoices / Failed invoices) × 100Target
> 50% with Smart Retries + dunning emails
Average Revenue Per Account (ARPA)
Average monthly revenue per paying account. Tracks whether revenue quality is improving over time.
Formula
ARPA = Total MRR / Number of active accountsTarget
Rising ARPA signals successful upsell to higher-tier plans
▸ Billing Mistakes
Six billing mistakes that create revenue leakage or security risks
These are the six most common and most expensive billing implementation mistakes across SaaS products. Every one is preventable and costly once users are live.
Trusting the Stripe Checkout redirect URL as payment confirmation
Consequence
The success_url redirect fires even if the user manually navigated to the URL without paying. Fraudulent access to paid features is trivially easy if billing state is set on redirect rather than webhook.
4Byte Standard
Never set subscription state on the success_url redirect. Only update subscription state via webhook handlers after receiving invoice.payment_succeeded or customer.subscription.created with status: active.
Not making webhook handlers idempotent
Consequence
Stripe retries webhooks up to 3 days for failed deliveries. A non-idempotent handler processing the same invoice.payment_succeeded twice could send duplicate receipts, double-credit accounts, or throw duplicate key errors.
4Byte Standard
Check for existing records before inserting. Use upsert operations. Store processed event IDs in a webhook_events table and skip already-processed events.
Calling the Stripe API on every authenticated request to check subscription status
Consequence
Stripe API calls add 100–300ms latency to every request. At scale this becomes a significant performance bottleneck. The Stripe API also has rate limits that will be hit under load.
4Byte Standard
Sync subscription state to your database via webhooks. Read subscription status from your own database on every request — not from Stripe. The database query is 10–50× faster.
Not verifying Stripe webhook signatures
Consequence
Without signature verification, anyone can POST fake webhook events to your endpoint. A malicious actor could fire a fake invoice.payment_succeeded event and upgrade themselves to a paid plan for free.
4Byte Standard
Always call stripe.webhooks.constructEvent(rawBody, sig, webhookSecret) before processing any event. This verifies the HMAC signature and prevents event forgery.
Revoking access immediately when cancel_at_period_end = true
Consequence
When a customer cancels but still has days remaining in their billing period, they've already paid for that time. Revoking access immediately breaks trust and violates reasonable user expectations.
4Byte Standard
Distinguish between cancel_at_period_end (scheduled cancel — maintain access until period_end) and subscription.deleted (immediate cancel — revoke access now). Only revoke on the webhook that fires at period end.
No dunning sequence for failed payments
Consequence
Without dunning, every failed payment results in immediate churn. Stripe's Smart Retries alone without customer communication recovers far less revenue than retries combined with a structured email sequence.
4Byte Standard
Build a 3-email dunning sequence triggered by invoice.payment_failed webhook. Provide a direct link to the Stripe Billing Portal for one-click card update. Stripe Smart Retries handles the technical retry; emails handle the human recovery.
▸ FAQ
Frequently asked questions about SaaS billing systems
How do you implement billing in a SaaS product?+
SaaS billing is implemented using Stripe — the industry standard payment infrastructure. The core components are: Stripe Checkout for payment collection, Stripe Subscriptions for recurring billing management, Stripe Webhooks for syncing subscription state to your database, and Stripe Billing Portal for self-service customer management. 4Byte Agency integrates all four components in every SaaS product we build.
What is the best pricing model for a SaaS product?+
The best SaaS pricing model depends on your product type. Per-seat (per-user) pricing works well for collaboration and team tools. Flat-rate pricing is simplest to understand and works for single-user products. Usage-based pricing aligns cost with value and scales well for API and infrastructure products. Most SaaS products start with flat-rate or per-seat tiers and add usage-based components post-launch.
How do Stripe webhooks work in a SaaS product?+
Stripe webhooks are HTTP POST requests Stripe sends to your server when subscription events occur — payment succeeded, payment failed, subscription cancelled, trial ending. Your webhook handler verifies the Stripe-Signature header, processes the event, and updates your database subscription state. The handler must be idempotent — safe to receive the same event multiple times — because Stripe retries failed deliveries.
What is dunning in SaaS billing?+
Dunning is the process of recovering failed subscription payments through automated retry logic and customer communication. When a payment fails (expired card, insufficient funds), dunning sends a sequence of emails asking the customer to update their payment method, while Stripe automatically retries the charge on a configurable schedule. Well-implemented dunning recovers 10–20% of churning revenue.
Should a SaaS MVP include billing from day one?+
Yes — billing must be included in the SaaS MVP. A product without billing is a demo, not a business. Even if you offer a free trial, requiring a credit card gate validates willingness to pay. Stripe integration in an MVP takes 3–5 days and provides the most important validation signal — whether users will actually pay for the product.
How does 4Byte Agency implement SaaS billing?+
4Byte Agency implements billing using Stripe Checkout and Stripe Subscriptions, synced to the database via webhook handlers. We build subscription lifecycle management, trial logic, plan upgrade/downgrade flows, a Stripe Billing Portal integration for self-service, and dunning email sequences via Resend. Billing is integrated in week 3 of every 28-day SaaS build.
▸ Related Resources
Go deeper into SaaS product infrastructure
▸ Ship billing that works from day one
Need your SaaS billing system built correctly and securely?
We implement the complete Stripe billing stack — Checkout, Subscriptions, Webhooks, Billing Portal, dunning sequences, and feature gating — in week 3 of every 28-day SaaS build. Using the exact patterns from this guide, refined across 45+ products.