SaaS Engineering12 June 2026 · 12 min readUpdated 21 June 2026

SaaS Billing and Payments: Stripe, Subscriptions, and Pricing

How to build revenue infrastructure for SaaS with Stripe: subscription billing, usage-based metering, and a pricing page that actually converts paying customers.

SaaS Billing and Payments: Stripe, Subscriptions, and Pricing

To set up Stripe subscription billing for a SaaS, you create products and recurring prices in Stripe, send customers through Stripe Checkout or the Payment Element, then treat your Stripe webhook handler as the single source of truth for what a customer is actually paying for. Your own database stores a copy of the subscription state, but Stripe events — not your front-end success page — decide when access is granted, downgraded, or revoked. Get the webhook contract right and almost everything else in billing becomes manageable.

This is the hub for everything revenue-related on a SaaS: how money moves through Stripe, how you meter usage when a flat monthly fee is the wrong model, and how you package and present pricing so the right plan gets picked. It is anchored by real production work — BookBed, Callidus, and Pizzeria Bestek all ship Stripe in production — not theory.

Key takeaways

  • Stripe webhooks are the source of truth. The browser redirect after checkout can be closed, blocked, or never fire. Subscription state in your database must be driven by customer.subscription.* and invoice.* webhook events, made idempotent so retries do not double-apply.
  • Pick the billing model before the schema. Flat per-seat, usage-based metering, and hybrid all imply different data structures. Migrating between them mid-flight is painful, so decide based on how customers perceive value.
  • Checkout vs. embedded is a real trade-off. Stripe Checkout is the fastest path to revenue and offloads PCI scope; the embedded Payment Element gives you a branded in-app flow at the cost of more front-end work.
  • The pricing page is billing's front door. Tier naming, anchoring, and the annual toggle move revenue as much as the code behind them.
  • Test mode and webhook signature verification are non-negotiable. Every Stripe integration should be built and rehearsed end-to-end in test mode with the Stripe CLI before a single live key touches production.
  • Dunning and failed-payment recovery are part of billing, not an afterthought. Involuntary churn from expired cards quietly eats revenue if you do not handle invoice.payment_failed and Smart Retries.

How does Stripe subscription billing actually work end to end?

The mental model that keeps Stripe integrations sane is this: Stripe owns the money and the schedule, your app owns access. You define the catalog (products and prices) in Stripe, you create a Customer object for each account, and you attach a Subscription that links that customer to one or more recurring prices. From there Stripe handles the invoicing cadence, proration, tax (via Stripe Tax), and card retries. Your job is to listen.

The canonical flow looks like this:

  1. A visitor picks a plan on your pricing page and clicks subscribe.
  2. You create a Checkout Session (or a Subscription with the Payment Element) server-side and send the customer to pay.
  3. Stripe charges the card, creates the subscription, and fires webhook events.
  4. Your webhook handler verifies the signature, reads the event, and updates the customer's plan and entitlements in your own database.
  5. Your app reads its own database — never Stripe directly on the hot path — to decide what features the user can access.

That last point matters for performance and reliability. You do not want every authenticated request hitting the Stripe API to ask 'is this person paid?' You cache the answer in your database, kept fresh by webhooks. The deep mechanics — session creation, the exact event list, proration on upgrades, the customer portal — live in the dedicated guide on setting up Stripe subscription billing for SaaS.

In the SaaS products I have built, this pattern repeats. BookBed, a property-management platform built solo in six months on Flutter, Firebase, and Stripe, syncs availability with bidirectional iCal sync across booking channels — and the entire paid layer rides on Stripe subscriptions whose state is mirrored into Firestore by a webhook function. Callidus, a clinic SaaS built in about 10 weeks on React and Firebase, does the same thing behind per-tenant Firestore security rules so that one clinic can never read another clinic's billing or patient data. The Stripe wiring is the same shape every time; what changes is the entitlement model on top of it.

Should you use Stripe Checkout or the embedded Payment Element?

This is the first real decision and people overthink it. Stripe Checkout is a hosted, Stripe-maintained payment page. You redirect to it, the customer pays, they come back. It supports subscriptions, one-time payments, trials, coupons, tax, and Apple/Google Pay out of the box, and because the card form lives on Stripe's domain, your PCI compliance scope collapses to the simplest tier. For a first paid version of a product, Checkout is almost always the right call — it is the difference between shipping billing this week and shipping it next month.

The embedded Payment Element is the alternative: Stripe's pre-built, PCI-compliant card UI rendered inside your own page. You keep the customer on your domain, you control the surrounding layout, and you can build a multi-step in-app upgrade flow. The cost is more front-end code, more states to handle (requires_action for 3D Secure, for example), and more testing. For Pizzeria Bestek — a React and Supabase web app shipping in four languages — keeping checkout inside the branded flow mattered for the experience, so the embedded approach earned its extra work.

A simple rule: if your goal is to validate that people will pay at all, use Checkout. If billing is a mature part of a product where the upgrade moment is itself a designed experience, the Payment Element is worth the investment. You can always start with Checkout and migrate later — the subscription objects and webhooks are identical either way, so the migration only touches the front end.

When should you charge a flat fee versus usage-based pricing?

Flat per-seat or per-tier pricing is simple to build, simple to forecast, and easy for customers to understand. It works when value scales roughly with the number of people using the product, or when usage is predictable enough that a tier ceiling feels fair. Most early-stage SaaS should start here precisely because it is the cheapest model to implement and the easiest to reason about.

Usage-based (metered) billing makes sense when the cost you incur or the value you deliver tracks consumption — API calls, messages sent, gigabytes processed, bookings handled, documents generated. The customer pays for what they use, which lowers the barrier to start and aligns price with value, but it is meaningfully harder to build: you have to record usage events reliably, aggregate them, report them to Stripe before each billing period closes, and handle the reporting failures gracefully so a dropped event does not become a billing dispute. The full architecture — meters, usage records, aggregation, idempotent reporting — is covered in the usage-based billing and metering guide.

Hybrid models — a base platform fee plus metered overage — are common in mature B2B SaaS and combine the predictability of a floor with the upside of consumption. They are also the hardest to build and explain, so reach for hybrid only once you have evidence that pure flat or pure usage is leaving money on the table or scaring off customers. The decision is a product and pricing question first, an engineering question second; the schema follows the model, never the other way around.

Why are webhooks the most important part of billing?

Every hard billing bug I have seen traces back to trusting the wrong signal. The browser redirect to your success URL is a courtesy, not a guarantee. The customer can close the tab, lose connection, or have an ad blocker eat the redirect — and the payment still went through. If your code grants access on the success page, you will have paying customers locked out and free riders who faked the URL. Access must be granted by webhooks, server to server, signed and verified.

A production-grade webhook handler does four things without exception. It verifies the Stripe signature on every request so forged events are rejected. It is idempotent — Stripe retries deliveries, so processing the same event twice must not create two subscriptions or send two emails; you store processed event IDs and skip duplicates. It responds with a 2xx fast and does the heavy work asynchronously, because Stripe times out slow endpoints and retries them, compounding the load. And it has a dead-letter path so an event that fails repeatedly lands somewhere a human can inspect it rather than vanishing.

This is not billing-specific wisdom — it is the same reliability discipline that every webhook in a system needs, which is why it sits at the heart of the broader SaaS backend infrastructure work covering jobs, webhooks, email, and admin tooling. Billing is simply the place where getting it wrong costs you money the most directly. In BookBed, the same idempotent-handler pattern that processes Stripe subscription events also processes the iCal sync events; the discipline transfers cleanly.

How do you keep one tenant's billing isolated from another's?

In multi-tenant SaaS, billing data is among the most sensitive a tenant owns — what they pay, when, on which plan. A leak here is both a privacy failure and a competitive one. The non-negotiable rule is that the Stripe customer ID, subscription state, and invoice history for tenant A must be unreachable by tenant B, enforced at the data layer rather than by hoping the application code always filters correctly.

In Callidus, this is done with per-tenant Firestore security rules: each clinic's documents carry a tenant identifier, and the rules reject any read or write where the requester's tenant does not match. Billing state lives under the tenant's own subtree, so isolation is structural, not conditional on a WHERE clause that a future query might forget. On a Postgres stack the equivalent is row-level security keyed on the tenant. Which approach fits depends on the tenancy model you chose, and that choice has consequences far beyond billing — it is the central topic of the multi-tenant SaaS architecture pillar on data isolation, tenancy, and auth. Bolt billing onto whatever isolation model the rest of the product already enforces; never invent a second, weaker one just for payments.

What makes a pricing page convert?

The pricing page is where all of this billing engineering meets the buyer, and it converts or loses revenue independently of how clean the Stripe code behind it is. A few things move the needle consistently. Three tiers with a clearly recommended middle option uses anchoring to make the middle feel reasonable. Annual billing presented as the default, with the monthly equivalent shown as a saving, lifts cash collected up front and reduces churn. Plan names and feature lists written in the customer's language — outcomes, not feature jargon — let a buyer self-select in seconds. And the single most-converting plan should have the least friction to start.

There is a real craft to this that goes well beyond layout, and it is worth its own treatment: the B2B SaaS pricing page that converts guide breaks down tier structure, anchoring, the annual toggle, social proof placement, and the copy patterns that reduce hesitation. Pricing also connects upward to how you scope and cost the work in the first place — if you are still figuring out what a build should cost before you can price it, the software development cost and pricing pillar covers estimation for web apps and SaaS. A pricing page is a conversion surface; treat it like one, test it, and do not let it be an afterthought bolted on after the billing code is done.

How do you handle failed payments and involuntary churn?

A surprising share of SaaS churn is involuntary — cards expire, get reissued after fraud, or are declined for insufficient funds. The customer never decided to leave; the payment just failed and nobody recovered it. Handling this well is pure profit because these are customers who already wanted to stay.

Stripe gives you the tools: Smart Retries reattempts failed charges on an optimized schedule, the invoice.payment_failed webhook lets you trigger your own dunning emails, and the customer portal lets users update a card without contacting support. A solid setup retries automatically, emails the customer with a one-click link to fix their card, sets a grace period before downgrading access, and only then revokes. The grace period is the difference between recovering a customer and creating a frustrated ex-customer who churned over a $0.00 problem of their own bank's making. This recovery loop is billing infrastructure, and it ties into the broader retention story in the SaaS UX and growth pillar on onboarding, activation, and conversion — keeping a paying customer is far cheaper than acquiring a new one.

How does AI-augmented work fit into building billing fast?

Billing has a lot of well-trodden surface area — webhook handlers, test fixtures, the customer portal wiring, dunning email templates — and that is exactly where an AI-augmented workflow pays off. The integration patterns are mature and well documented, so the routine scaffolding can be generated and reviewed quickly, leaving more attention for the parts that are genuinely yours: the entitlement model, the edge cases in proration, the pricing logic. That is how BookBed shipped a full booking-management SaaS with Stripe in six months solo, and how Callidus shipped a clinic platform in about 10 weeks — faster than a typical agency timeline — without skipping the webhook discipline or the tenant isolation. The method is its own topic in the AI-augmented development pillar; the relevant point for billing is that the boilerplate is automatable and the judgment is not.

If you are at the very start and billing is one of many systems you have not built yet, the SaaS MVP development guide puts billing in sequence with auth, the database, and the rest of a first version, so you add Stripe at the point where it actually earns its complexity rather than too early.

Where to go next

Billing is a system, not a button. The shape is consistent: Stripe owns money and schedule, webhooks are the source of truth, your database caches entitlements, and the pricing page is the conversion surface that feeds it all. Decide the billing model from how customers perceive value, build and rehearse the whole thing in test mode, and treat failed-payment recovery as revenue rather than support overhead.

From here, go deep on the piece you need now — subscription billing setup for the core wiring, usage-based metering if a flat fee is the wrong model, or the pricing page that converts to make sure the front door is doing its job. If you would rather have someone who has shipped Stripe in production across multiple live products build it with you, get in touch.

More in this guide

DL

Dusko Licanin

Full-Stack Developer · Banja Luka, Bosnia

Full-stack developer shipping SaaS MVPs, web apps, and mobile apps 2× faster than agencies using AI-augmented workflows. Live portfolio: BookBed, Callidus, Pizzeria Bestek.

Frequently Asked Questions

Do I need to store subscription data in my own database if Stripe already has it?

Yes. Calling the Stripe API on every authenticated request to check payment status is slow and fragile. Cache the subscription state and entitlements in your own database, keep that copy fresh with webhook events, and read your database on the hot path. Stripe stays the source of truth; your database is the fast, local mirror.

What is the most common Stripe billing mistake?

Granting access on the browser success page instead of via webhooks. The redirect after checkout can be closed, blocked, or never fire while the payment still succeeds. Access must be granted server-to-server by verified, idempotent webhook handlers listening to customer.subscription.* and invoice.* events.

Should a new SaaS use flat pricing or usage-based billing?

Most early-stage SaaS should start with flat per-seat or per-tier pricing because it is the cheapest to build and easiest for customers to understand. Move to usage-based or hybrid metering only when consumption clearly tracks value or cost, since metering adds real engineering complexity around recording, aggregating, and reporting usage.

How do you keep one tenant's billing data isolated from another's in multi-tenant SaaS?

Enforce isolation at the data layer, not in application filters. On Firebase, per-tenant security rules reject any read or write where the requester's tenant does not match, as in Callidus. On Postgres, row-level security keyed on the tenant does the same. Billing state should live under whatever isolation model the rest of the product already enforces.