We've shipped Stripe Billing on a Rails-based AI SaaS, two B2B vertical SaaS products, and one usage-metered API platform. Each had the same pattern: the founder thought billing was a week of work, then spent two months in production debugging dunning emails, proration math, and tax compliance. The patterns below are what we ship from day one to keep the second month from happening.
The short answer
Stripe Billing is Stripe's product for recurring subscription revenue — distinct from Stripe Connect, which is for marketplaces and platforms paying out third parties. For a typical SaaS, you'll use Stripe Billing with Stripe Tax and the hosted Customer Portal, build idempotent webhook handlers for the invoice lifecycle, and ship a dunning flow before turning on real payments. Total scope: 2-3 weeks of focused engineering.
Watch first: a production Stripe subscriptions walkthrough
Before the architecture, this is the cleanest end-to-end walkthrough we've seen — Hussain Mehmood builds a working subscription SaaS on Stripe in 50 minutes, including the customer portal and webhook handling. Skim it at 1.5x.
Pick a pricing model before you write code
This is the most common founder mistake — start shipping subscription code, decide pricing model later. You end up with a database schema and webhook handlers that don't match how you eventually want to charge. Decide first, then build.
| Model | When it fits | Stripe primitives |
|---|---|---|
| Per-seat | Workspace tools, internal team software | Subscription + quantity on subscription item |
| Tiered (flat) | Starter / Pro / Business plans, classic SaaS | Multiple Prices on one Product, swap on upgrade |
| Usage-based (metered) | APIs, infra, AI products billed per call/token | Metered Price + meter events |
| Hybrid (base + overage) | $49/mo includes 10K calls, then $0.001/call | Licensed Price + Metered Price on same subscription |
Don't overthink usage-based unless your unit economics actually justify it. Per-seat and tiered cover 80% of SaaS launches and are 5x simpler to operate. Migrating from per-seat to usage later is straightforward if you keep your subscription model clean.
The minimum production setup
Three Stripe concepts you'll touch every day: Products (the thing you sell), Prices (how much, how often, in which currency), and Subscriptions (a customer's active commitment to a Price). A typical SaaS has one Product per plan tier and 2-3 Prices per Product (monthly USD, annual USD, monthly EUR).
The flow on the server side for a new signup with a paid plan:
# Create Stripe Customer when user signs up
customer = Stripe::Customer.create(
email: user.email,
metadata: { user_id: user.id }
)
user.update!(stripe_customer_id: customer.id)
# Create Checkout Session for the subscription
session = Stripe::Checkout::Session.create(
customer: user.stripe_customer_id,
mode: 'subscription',
line_items: [{ price: price_id, quantity: 1 }],
success_url: success_callback_url,
cancel_url: pricing_url,
automatic_tax: { enabled: true }, # Stripe Tax
tax_id_collection: { enabled: true }, # for B2B VAT
allow_promotion_codes: true,
billing_address_collection: 'required' # required for tax
)
redirect_to session.url, allow_other_host: true
The Checkout Session is hosted by Stripe — they handle PCI, 3D Secure, SCA, and the actual card form. You handle the redirect and the webhook that fires after.
Customer portal vs custom dashboard
For 90% of SaaS, use Stripe's hosted Customer Portal. It's free, handles plan changes, payment method updates, invoice downloads, and cancellation — all the screens you don't want to build. Enable it in the Stripe Dashboard, generate a portal session, redirect:
session = Stripe::BillingPortal::Session.create(
customer: user.stripe_customer_id,
return_url: account_url
)
redirect_to session.url, allow_other_host: true
When to build a custom dashboard instead: you need to display per-seat usage charts inside your app, you want cancellation flows with retention offers ("get 50% off for 3 months"), or you're on an enterprise plan where the hosted portal's branding limits matter. For everything else the hosted portal saves 2-4 weeks of engineering.
Webhooks: the invoice lifecycle that actually matters
The Stripe Billing docs list 30+ webhook events. You need maybe 6 in production. The rest are noise unless you have a specific need.
| Event | What to do |
|---|---|
checkout.session.completed | Activate the user's subscription in your DB |
customer.subscription.updated | Sync plan, status, current_period_end |
customer.subscription.deleted | Downgrade to free or revoke access |
invoice.payment_succeeded | Extend access period, send receipt |
invoice.payment_failed | Trigger dunning email flow |
customer.subscription.trial_will_end | Send trial-ending notification 3 days before |
Every handler must be idempotent — Stripe delivers webhooks at-least-once, and retries on 5xx. Track the event ID and skip if you've seen it:
def create
event = Stripe::Webhook.construct_event(
request.body.read,
request.headers['Stripe-Signature'],
ENV['STRIPE_WEBHOOK_SECRET']
)
return head :ok if ProcessedStripeEvent.exists?(stripe_event_id: event.id)
ActiveRecord::Base.transaction do
ProcessedStripeEvent.create!(stripe_event_id: event.id)
handle(event)
end
head :ok
end
This is the same idempotency pattern we cover at length for marketplace platforms in our Stripe Connect production gotchas guide — same root issue, both Stripe products require it.
Dunning that doesn't churn paying customers
Card declines happen on roughly 4-6% of recurring charges in production. Most are not actual cancel intent — expired cards, temporary fraud locks, insufficient funds that resolve in 48 hours. Your dunning flow decides how much of that revenue you save.
Stripe handles the retry schedule automatically — by default, three retries over a week. Configure this in Dashboard → Billing → Subscriptions settings. Pick the "Smart Retries" option (Stripe ML picks retry timing) over fixed retry schedules. We see a 15-25% recovery lift from Smart Retries on the SaaS clients we've migrated.
What you build on top:
- Email 1 — immediate on
invoice.payment_failed: "Your card was declined. Update payment method." Link to the Customer Portal. - Email 2 — day 3: "Still can't process your payment. Subscription pauses in 4 days."
- Email 3 — day 6: "Final notice. Subscription will be canceled tomorrow."
- In-app banner: a persistent payment-failure banner with a one-click portal link is worth 10x more than email.
- Grace period: give 7-10 days of continued access after the first failure. Cutting access on day 1 spikes involuntary churn and is hostile to legitimate users with travel/card-replacement scenarios.
Proration, tax, and usage-based metering
Proration trips up everyone. When a customer upgrades mid-cycle from $29 to $99, Stripe by default credits the unused portion of the $29 and charges the prorated portion of $99 immediately. Default is usually correct. Where it bites: downgrades. If a customer downgrades mid-cycle, Stripe credits the difference as a balance applied to the next invoice. They don't get a refund. Document this in your billing FAQ or expect support tickets.
The two config flags that matter: proration_behavior: 'create_prorations' (default) vs 'none' (no proration — change applies at next renewal). For most SaaS, create_prorations matches user expectations. For freemium tier swaps where you want changes to feel "free," use none.
Stripe Tax is non-negotiable in 2026. EU VAT, UK VAT, US sales tax in 40+ states, GST in India and Canada — calculating these by hand is a multi-month engineering project plus ongoing compliance. Stripe Tax charges 0.5% on transactions where it calculates tax (capped) and handles registration, calculation, and filing exports. Turn it on at launch — backfilling tax compliance after a year of revenue is a painful audit.
Metered billing for usage-based plans: every API call (or token, or storage GB) is a meter event you POST to Stripe. They aggregate, you bill. The big pitfall is meter event lag — Stripe aggregates on a delay, so the invoice doesn't always reflect the latest usage if you generate the invoice the moment the period ends. Build a 6-12 hour buffer or generate invoices on a daily cron after the period boundary.
The pre-launch SaaS billing checklist
Before your first paid customer:
- ☐ Pricing model picked and documented (per-seat / tiered / usage / hybrid)
- ☐ Products + Prices created in both test and live Stripe
- ☐ Checkout Session integration with Stripe Tax enabled
- ☐ Customer Portal session integration tested end-to-end
- ☐ Idempotent webhook handler for the 6 events above
- ☐ Smart Retries enabled in Dashboard settings
- ☐ Dunning email sequence (3 emails + in-app banner)
- ☐ Grace period of 7-10 days configured before access revocation
- ☐ Proration behavior decided and documented for upgrades and downgrades
- ☐ Stripe Tax enabled and tax registrations completed in your launch jurisdictions
- ☐ Test mode end-to-end: signup → upgrade → downgrade → failed payment → recovery → cancellation
- ☐ Webhook signature verification using
STRIPE_WEBHOOK_SECRET - ☐ Annual billing option offered (lifts ARR per customer 15-30%)
Miss any of those and you'll be debugging in production at the worst time — when you have real customers and real money on the line.
Where SaaS billing fits in the broader build
Billing is one slice. The full SaaS build also needs multi-tenancy, auth, instrumentation, and a delivery model. Our Rails SaaS builders guide walks through the broader stack, and the SaaS development cost breakdown covers the actual budget envelope from idea to revenue. If you're choosing between row-level, schema-level, and database-per-tenant isolation, our deep dive on Rails multi-tenancy strategies is the right starting point — billing design intersects with tenancy more than people expect.
For the AI SaaS case, we built the billing + usage metering layer for RankLoop on top of Stripe Billing — usage-based pricing for AI agent runs, with a Customer Portal swap for plan changes. The metering buffer pattern above is what kept their invoices accurate.
One external read worth bookmarking: the official Stripe Billing docs are well-maintained and worth reading end-to-end before architecture decisions. The Customer Portal configuration reference covers the dozen toggles you'll actually flip during setup.
FAQ: Stripe Billing for SaaS
Stripe Billing or Stripe Connect for SaaS?
Billing. Connect is for marketplaces and platforms that pay out third parties (an Airbnb-like platform paying hosts). A typical SaaS where you charge customers and keep the revenue is Billing territory. We covered the Connect-specific gotchas separately in our Stripe Connect marketplace guide if your product touches both.
How long does Stripe Billing take to set up in production?
For a single-tier or simple tiered SaaS with the patterns above, plan 2-3 weeks of focused engineering. Usage-based billing or complex hybrid models add 2-4 weeks for metering pipelines and edge-case dunning. Custom dashboards instead of the hosted portal add 2-4 more weeks.
What about Paddle, Lemon Squeezy, or Chargebee?
Paddle and Lemon Squeezy act as a Merchant of Record, which means they handle global tax compliance and chargeback liability for you — at a higher fee (5-8% vs Stripe's 2.9% + 30¢). Worth it for solo founders selling globally; usually not worth it once you have a finance team. Chargebee adds an orchestration layer on top of Stripe — useful for complex enterprise contract billing, overkill for most early SaaS.
How do we handle B2B annual contracts with NET 30 invoicing?
Stripe Invoicing (a Billing sub-product) supports manual invoice generation, NET payment terms, and bank transfer payments. Generate the invoice via API, email it via Stripe, customer pays by ACH or wire. Reconciliation back to the customer record is automatic if you use the Stripe Customer ID.
What's the biggest revenue-leakage source we see in early SaaS billing?
Three things, in order: no dunning flow (involuntary churn from card declines, costs 4-6% of MRR), no Stripe Tax (manual tax handling fails an audit and creates back-tax liability), and access not revoked on cancellation (paying customers tell their friends, friends use the product free for months). All preventable with the checklist above.
How we can help
At TechVinta we ship Stripe Billing end-to-end as part of our e-commerce and SaaS development work — pricing model design, Stripe integration, webhook handlers, dunning flows, and Stripe Tax setup. A clean Billing build for a typical SaaS takes 2-3 weeks. Usage-based or hybrid models take 4-6.
Stuck on Stripe Billing, or want a second pair of eyes on your subscription architecture before you launch? Get a free estimate — we'll review your current setup and propose a plan within 48 hours.