Back to Playbooks

Stop Letting Failed Payments Quietly Cancel Your Best Customers

Free

An end-to-end Spreeflo journey for recovering failed subscription payments, from custom billing events through multi-step dunning emails, conditional branching, and internal escalation, so you cut billing-failure churn while keeping high-value customers engaged and tagged correctly.

Industry

Niche

Pattern

Loading sequence...

On Monday morning, Maya opens her metrics for CartWizard, a Shopify upsell app doing around $90k MRR. Stripe shows 32 failed renewals in the last week. Historically, almost half of those stores never come back.

None of them opened a support ticket. Most still love the product. Their cards just expired, hit a limit, or got flagged. But because billing failure is “owned” by Stripe emails and a single generic reminder from the app, those merchants silently churn.

That is pure lifetime value leaking out the side of the business.

The sequence at the top of this page is the whole journey, end to end, for fixing that. It’s a payment-failure recovery flow built in Spreeflo: triggered by a payment_failed event, paced over seven days, with clear branching on whether the payment ultimately succeeds and an internal escalation for high-value accounts.

In this article, we’ll walk that journey node by node. The goal is simple: turn “oops, card failed” from a silent churn event into a recoverable engagement moment.

We’ll use CartWizard as our running example: a four-person team, $49/mo price point, 2,000 active stores, and a billing-failure problem that’s eroding what should be strong retention.

Start where the money actually breaks: the Custom Event trigger

Everything hinges on getting a clean, reliable signal when billing fails.

In Spreeflo, that’s a Custom Event called payment_failed, sent from your billing system into the Spreeflo API. Your server receives a webhook from Stripe, Paddle, or Shopify Billing, then forwards a simplified event to Spreeflo with properties like:

  • amount

  • plan_tier

  • is_recurring

  • failure_reason

In the journey at the top of the page, the entry point is a Custom Event trigger configured roughly as:

  • Event name: payment_failed

  • Property conditions: is_recurring is true (so you’re focusing on renewals, not first charges)

  • Re-enrollment: on, so every failed renewal can start the journey again once the previous run has finished

Re-enrollment matters here. A merchant’s card can fail this year, be fixed, and fail again eight months later. You want the full sequence each time, but you don’t want them in two parallel copies of the journey. Spreeflo’s mid-journey lock takes care of that: a store can’t be re-enrolled while they’re already moving through the flow.

Tag and timestamp the incident so the rest of your marketing respects it

The first thing the journey does after the trigger is label what just happened.

  1. Add Tag

  2. Update Contact Attribute

Apply tags such as billing-failed and at-risk. This makes it trivial to:

  • Exclude these stores from upbeat promo campaigns while their account is in trouble

  • Build segments like “Recovered after billing failure” later

The journey uses an Add Tag node for this.

Next, we drop a Update Contact Attribute node to set:

  • billing_status to the literal value past_due

  • last_billing_failure_at to “Set to now”

That billing_status field becomes the single source of truth for other journeys. A welcome-nurture flow, for example, can check “billing_status is not past_due” before sending a “here’s how to scale with us” email.

This is where Spreeflo’s belief shows up in practice: capture detail on every customer so you can speak to each uniquely. A merchant with billing_status = past_due should not be getting the same campaigns as a healthy subscriber.

Email 1: Immediate, calm, and laser-focused on the fix

Right after tagging, the journey sends the first reminder.

That’s a Send Email node wired to a template you build in the email builder. Configuration highlights:

  • From: your normal product sender identity (it should feel like the app talking, not the bank)

  • Send only once: on, so this exact email can’t be resent to the same contact by this journey

The content does the heavy lifting, but the automation ensures it goes out instantly, every time:

  • Subject that’s clear and non-threatening: “CartWizard couldn’t renew your subscription”

  • Body that:

  • States what happened (“Yesterday’s renewal on your Visa ending in 1234 didn’t go through.”)

  • Explains the consequence (“If we can’t charge successfully in the next 7 days, your upsell campaigns will pause.”)

  • Gives one single, obvious CTA: “Update billing details” with a direct link to your portal

The sequence then inserts a Time Delay of 2 days. This honors inbox sanity and Spreeflo’s pacing rules: you never send two emails back to back on the same path without some time or condition-based spacing.

If that first reminder is the only step you ever implemented, you’d probably recover some renewals. But we’re aiming to systematically reduce billing-failure churn, not hope.

Don’t nag people who’ve already paid: the first If/Else guardrail

After the 2-day Time Delay, not every merchant still has a problem. Some clicked the link in the first email and fixed their card within minutes. Others might have paid through a separate channel after seeing a Stripe message.

You do not want to send Email 2 to those people.

So the sequence hits an If/Else process node before any second touch. The condition uses Spreeflo’s segment builder to ask:

  • Has the Custom Event payment_succeeded triggered at least 1 time in the last 7 days?

If the answer is “yes”:

The journey routes them to a short “recovered” branch:

  • Remove Tag: drop billing-failed

  • Update Contact Attribute: set billing_status back to active

  • Optional Send Email: a simple “You’re all set, billing is up to date” confirmation

If the answer is “else”:

They clearly haven’t paid yet. The journey continues to Email 2.

This pattern repeats throughout the journey: before each nudge, an If/Else path checks whether the problem still exists. That’s the difference between thoughtful dunning and harassment.

Email 2: Reframe around value at risk

On the “still unpaid” branch, the next node is another Send Email, but only after that guardrail condition.

This second email lands around day 2 and is more about the value they’re about to lose than about the card itself:

  • Subject along the lines of: “Don’t let your upsell campaigns pause this week”

  • Body:

  • Reminds them how many orders or extra revenue CartWizard has generated in the last 30 days

  • States the concrete date when their app access will pause if billing stays unresolved

  • Again, gives the single clear CTA to update billing

Once Email 2 sends, the sequence adds another Time Delay, this time 3 days. We’re now at roughly day 5 from the original failure.

Before anything else happens, the journey hits a second If/Else with the same condition: “Did payment_succeeded happen in the last 7 days?”

  • If yes, they join the same “recovered” branch as before (through a Merge node that recombines paths before updating tags and attributes).

  • If no, they move toward the final reminder and internal escalation.

Day 5: Final reminder plus a human in the loop

By day 5, any still-failing account falls into one of two buckets:

  1. Small store that may or may not come back

  2. High-value merchant whose departure would really hurt

You want both to see a final warning, but only the latter truly justifies manual outreach. The journey handles that with two elements: a Multi-way Split and a Send Internal Email.

Split by value using Multi-way Split

First, a Multi-way Split process checks plan or revenue attributes captured on the contact. For example:

  • Branch “High-value”:

  • Else branch:

  • Custom attribute plan_tier is Enterprise

  • Or mrr is greater than 199

  • Everyone else

The conditions themselves are defined with the same segment builder you saw earlier, just embedded inside the Multi-way Split.

Email 3: “We’re about to pause your app”

On both branches, the next outward-facing step is Email 3: a straight-talking reminder that sets a clear deadline.

  • Subject: “We’ll pause CartWizard on [DATE] if we can’t bill your card”

  • Body:

  • Short recap of failed tries

  • Exact date (2 days from now) when the app will stop processing orders

  • Direct link to update billing

Again, this is a Send Email node with “send only once” enabled, and it’s never chained directly after another email without a delay.

Internal escalation for high-value merchants

Only the “High-value” path gets an internal nudge:

  • A Send Internal Email node sends a message to your team (for example, a shared billing inbox).

  • The email includes the merchant’s name, email, plan tier, and a link into your admin panel, all pulled from the contact record.

Now your CS or founder can decide whether to:

  • Reach out personally

  • Extend more time

  • Offer a quick call to sort out billing

The “Standard” value path can either skip this step or route to a different Send Internal Email with a softer ask, like “review this account when you have time.”

Both branches feed into a Merge node afterwards so the final resolution logic is shared.

The final branch: recovered vs. billing-churned

After the last external and internal nudges, the journey finishes with a clear classification. That’s what powers reliable metrics on recovered payments and billing-failure churn.

Here’s how that part of the sequence works:

  1. Wait Condition for up to 2 more days

  2. If/Else: did the payment ever succeed?

  3. On the “Recovered” branch:

  4. On the “Billing-churned” branch:

The journey parks the contact in a Wait Condition node with:

  • Condition: Custom event payment_succeeded triggered at least 1 time in the last 7 days

  • Timeout: 2 days

If they pay within those 2 days, the condition fires and the journey moves on immediately. If not, the timeout expires and the journey also moves on. Either way, they don’t sit in limbo forever.

Right after the wait, an If/Else checks the same condition:

  • Yes branch = “Recovered”

  • Else branch = “Billing-churned”

  • Remove Tag: billing-failed

  • Add Tag: billing-recovered

  • Update Contact Attribute:

  • billing_status to active

  • Optionally increment a recoveries_count numeric attribute

Some teams also send a short confirmation email here; others rely on the transactional “payment receipt” email from their billing system and skip extra messaging. Either works.

  • Add Tag: billing-churned

  • Update Contact Attribute: billing_status to cancelled

  • Optionally, update a churn_reason attribute to the literal billing_failure

You might also feed this branch into separate win-back journeys later or into a transactional cancellation email configured through Spreeflo’s transactional email. The important thing is that the payment-failure journey itself ends cleanly with an explicit state.

Now your churn dashboard can stop treating “cancelled” as a monolith. You know exactly which cancellations came from billing issues and which came from voluntary downgrades.

Measuring whether this is worth the effort

For a business like CartWizard, the math is usually compelling.

Before this journey, imagine the numbers looked like:

  • 50 failed renewals per month

  • 40% recovered on autopilot, 60% lost

  • Average $49/mo per account

That’s 30 stores quietly churning from billing failure each month, or about $1,470 in MRR. Over a year, more than $17k in ARR.

With the dunning journey live, your targets might be:

  • Recover 65–75% of failed renewals

  • Reduce billing-failure churn from 30 accounts to 12–18 per month

Even a move to 70% recovery saves about 16 stores a month for CartWizard, or ~$784 MRR. The sequence pays for itself quickly, and it does it without hiring anyone.

Because the journey tags and updates every outcome, you can use Spreeflo’s platform for campaigns and journeys plus the segment builder to track:

  • Recovery rate by plan tier
    Segment: billing-recovered tag and plan_tier is Enterprise

  • Time-to-recovery distribution
    Approximate by comparing last_billing_failure_at and billing_status changes

  • Churn composition
    Compare counts of billing-churned vs. other churn reasons

Those insights feed back into both product and finance conversations. You can tune retry windows, decide where manual outreach is worth it, and forecast LTV with more confidence.

Why this isn’t “just billing” – it’s engagement

Payment failures feel like a finance problem, but they’re really an engagement problem.

Most of the merchants in this journey are still getting value. They installed your Shopify app, integrated it, saw real revenue from it. Their card simply got in the way. Treating that as a silent, automated cancellation is exactly the kind of lifetime-value leak that small SaaS teams can’t afford.

A well-built dunning journey does three things for a founder-led app business:

  • It saves the revenue that should have been yours anyway.

  • It adds human eyes to the few accounts that truly matter.

  • It keeps your broader marketing respectful of someone’s real status.

Spreeflo gives you the pieces: the Custom Event trigger from your billing system, the Send Email and Send Internal Email actions, the If/Else, Multi-way Split, and Wait Condition logic, all wired into one contact record and one automation canvas.

Build the sequence once, let it run quietly, and watch your billing-failure churn shrink. That isn’t a flashy growth hack. It’s the slow, steady work of nurturing engagement at the exact moment it’s at risk—which is where most of the lifetime value in a SaaS like yours is won or lost.