I Launched My SaaS, Set Up Stripe… and Was Losing 30%+ of Recurring Revenue Without Knowing It

Programming· 4 min read

I Launched My SaaS, Set Up Stripe… and Was Losing 30%+ of Recurring Revenue Without Knowing It

It was a Tuesday night. I was scrolling through the Stripe dashboard with coffee in hand and something didn’t add up.

Some customers had a past_due status. Others were outright canceled. Users I thought were active. Users who’d had no access for days without anyone notifying them.

There was no bug in the code. The problem was simpler — and bigger: I’d never properly configured how Stripe handles failed payments. And on top of that, those users had no way to update their card without writing to me directly.

Spoiler: it wasn’t easy to admit. But I’m sharing it because it’s more common than it seems.

Hole #1: Failed Payments You Never Recover

Cards fail. All the time. Reached credit limit, expired card, bank blocking an international charge. This isn’t an edge case — it’s everyday life for any subscription SaaS.

What many developers don’t know is that Stripe has a system called Smart Retries that uses machine learning to choose the best time to retry a charge. It’s not a blind retry 24 hours later — it analyzes banking behavior patterns to maximize success probability.

In 2024, according to Stripe’s own data, this system recovered billions in revenue for subscription businesses that would otherwise have lost those customers to involuntary churn.

The data point that hit me hardest: businesses that don’t handle failed payments well can lose more than 30% of their recurring revenue this way. Not from users who decide to leave. From users who want to stay but whose card simply failed.

How to activate it: In your Stripe dashboard, go to Billing → Settings → Automatic collection. There you can configure Smart Retries and define what happens after N failed attempts (cancel, pause, send email).

But activating it alone isn’t enough. You need to listen to the right webhooks.

Hole #2: Poorly Implemented Webhooks

This is the most common error I see. And the most dangerous.

Many developers set up the webhook endpoint, test it with a test event, and call it done. Wrong.

There are three things you must do:

1. Verify every webhook signature

Never trust a webhook payload without verifying it came from Stripe. It’s trivial to implement and critical for security:

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Skip this step and anyone can send you a fake POST and change a user’s subscription status.

2. Handle idempotency

Stripe can send the same event more than once. Your handler must be idempotent — if you process the same invoice.paid twice, you can’t grant premium access twice or double-charge.

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

3. The events that actually matter

For a basic subscription, you must listen to at least these:

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Hole #3: Users Who Can’t Self-Manage Their Subscription

This cost me more support tickets than I care to remember.

“How do I cancel my plan?”
“Can I downgrade to the basic plan?”
“I need to update my card.”

All manual messages. All handled by me. All preventable.

Stripe has a Customer Portal that solves this at the root. It’s a hosted page that Stripe manages for you where your users can:

  • Update or change their payment method
  • View their invoice history
  • Upgrade or downgrade their plan
  • Cancel their subscription

Integrating it takes minutes:

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

And on the client:

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

One button. One API call. Zero support tickets about billing.

The 2026 Context: Subscription Patterns That Matter

More than 60% of SaaS products today offer usage-based billing. Not just flat-rate. The market has moved toward more flexible models: per-seat, per-event, per-volume.

Stripe supports all these patterns natively. But importantly, the Customer Portal supports them too — the user can see exactly what they’re consuming and why they’re being charged what they’re charged.

In the European context, this also intersects with GDPR: you need to be able to give users their invoice history and the ability to manage their relationship with you. The Customer Portal handles part of this obligation without you having to build it from scratch.

Where to Start (Without Overwhelming Yourself)

If you have Stripe in production right now, the order is this:

  1. Audit your webhooks. Are you verifying signatures? Handling idempotency? If not, that’s first.
  2. Enable Smart Retries in your dashboard. It’s a toggle. Zero effort cost.
  3. Implement the Customer Portal. An afternoon’s work. Zero billing-related support tickets after.

That’s it. No epic refactors. No changing your stack.

I learned this the hard way. You don’t have to.

Brian Mena

Brian Mena

Software engineer building profitable digital products: SaaS, directories and AI agents. All from scratch, all in production.

LinkedIn