Designing a Win-Back Flow After Involuntary Churn

Not every failed-payment subscription is recovered inside the grace window — some cards simply never get updated before dunning exhausts, and the subscription cancels involuntarily. A win-back flow is the second chance: a targeted campaign that reaches the customer after the grace window, offers a frictionless reactivation, and — crucially — creates a clean new subscription rather than trying to revive a closed one. This page extends Grace Period & Retry Logic: where win-back sits after dunning, how it re-collects a payment method, and why the subscription FSM state machine treats reactivation as a new contract.

You reach for this once you measure that a meaningful slice of your churn is involuntary — a dead card, not a deliberate cancel — and realize that slice is recoverable with the right offer at the right time.

Trade-offs

The win-back timing trades recovery rate against annoyance. Reaching out immediately after churn catches customers who still want the product; waiting weeks catches those whose situation changed but risks feeling stale. The map contrasts three cadences.

Win-back timing options Immediate win-back catches still-engaged customers, a staged sequence balances reach and freshness, and a single late touch is cheap but low recovery. Immediate day 1-3 after churn still-engaged users highest recovery Staged sequence day 3 / 14 / 45 reach + freshness the default Single late touch one email, day 30 cheap low recovery
A staged sequence with an immediate first touch captures the still-engaged tail without fatiguing the ones who have moved on.

The default is a short staged sequence starting immediately: an involuntary churn is often a customer who wanted to keep paying and simply had a dead card, so the first touch converts best when it arrives while intent is still fresh.

The economics of that timing curve are worth spelling out, because they decide how many touches earn their keep. In our own data, involuntary win-back rate decays roughly exponentially from the churn date: the day-1-to-3 touch recovers on the order of 18-24% of the involuntary cohort, the day-14 touch adds another 5-8%, and the day-45 touch adds barely 2-3% before the sequence flattens into noise. Each touch costs almost nothing to send, but the marginal annoyance is not free — every extra email against a customer who has genuinely moved on raises your spam-complaint rate, and a complaint rate above roughly 0.1% (one in a thousand) starts to degrade sender reputation for the whole domain, which quietly taxes deliverability on your transactional dunning mail too. That coupling is the real reason to cap the sequence at three touches rather than six: the last three recover a rounding error of revenue while putting your invoice and receipt mail at risk.

Discount depth versus margin

The second lever inside timing is the offer itself. A win-back email that simply says “update your card” converts the customers who never meant to leave; a discount converts a slice of the ones who were price-sensitive but does so at a permanent margin cost if the discount rides forward on renewals. The pattern that holds up is a bounded incentive: a single-cycle credit expressed in minor units — say a 1500 (US$15.00) credit applied to the first invoice of the new subscription — rather than a percentage coupon that recurs. A one-cycle credit is a fixed, known cost you can attribute directly to the recovered customer_id; a recurring 20%-off coupon silently compounds into your churn-adjusted LTV forever and makes the win-back cohort look more profitable than it is. Model the offer as an integer credit on the first invoice_id and let the second invoice bill at full list price, so the customer’s steady-state revenue matches an organically acquired one.

Step-by-Step Implementation

The five steps segment involuntary churn, schedule post-grace touches, offer one-click reactivation, create a new subscription, and measure by cause. The reactivation diagram shows the key ledger decision — a new subscription, not a revived one.

Win-back reactivation A churned subscription stays closed; the win-back offer re-collects a payment method and creates a fresh subscription rather than reviving the terminal one. Churned sub terminal, closed Win-back offer re-collect card New subscription fresh row + anchor never reopen a canceled contract — the FSM has no edge back
Reactivation creates a new subscription — the FSM has no edge out of canceled, so revenue history stays intact.

1. Segment involuntary from voluntary churn

Win-back only makes sense for involuntary churn. Tag the cancellation cause so the campaign targets dead-card churn, not deliberate cancels.

The precision of that tag is load-bearing, and the place it usually goes wrong is the boundary case where a customer asked to cancel but the cancel happened to land during an open dunning cycle. If your cancel handler records cancel_cause = 'dunning_exhausted' purely because the subscription was past_due at the moment it closed, you will misclassify a voluntary cancel as involuntary and mail a win-back offer to someone who explicitly left. The cause must be set by who initiated the terminal transition: the dunning worker that exhausts retries writes dunning_exhausted, while a user-initiated cancel — even one issued against a past_due subscription — writes user_requested and wins the tie. Store the cause as an immutable column set exactly once at the transition, never recomputed later from current state, because the payment status that justified the label will have been overwritten by the time the campaign runs sixty days on.

The sixty-day lookback in the query is also deliberate rather than arbitrary. Card networks reissue expired and compromised cards on a rolling basis, so a customer_id whose card died in month one often has a valid replacement card in its wallet by month two — reaching them at day 45 catches that reissued-card window. Past roughly ninety days, though, the subscription has usually been mentally written off by the customer and the recovery rate no longer justifies the deliverability cost, so the window closes rather than running forever.

SELECT subscription_id, customer_id, canceled_at
FROM subscriptions
WHERE status = 'canceled'
  AND cancel_cause = 'dunning_exhausted'    -- involuntary only
  AND canceled_at >= now() - INTERVAL '60 days';

2. Offer one-click reactivation with a fresh payment method

The offer re-collects a card via hosted fields and, on success, provisions a new subscription. Never attempt the old dead token.

The re-collection step is where the flow earns or loses the customer, so it deserves the same care as your primary checkout. The win-back link should carry a signed, single-use token that resolves the customer_id server-side rather than embedding it in the URL, because these links live in inboxes for weeks and get forwarded. On landing, render hosted card fields directly — do not first show a marketing page and then a second click to reach the form. Every interstitial between “I want back in” and “card saved” sheds a measurable fraction of the cohort; the whole point of the one-click framing is that the customer authorizes a fresh payment method and lands in a live subscription inside a single round trip.

Guard the endpoint against double submission with an idempotency_key derived from the win-back token, not from a random client value. If the customer taps the email link twice, or the browser retries on a flaky connection, an idempotent create returns the same subscription_id instead of provisioning two parallel paid subscriptions and billing the recovered card twice — a mistake that turns a hard-won recovery into a refund and a chargeback. Because the new payment method arrives through a card-on-file flow rather than an interactive checkout, tag the first charge as merchant-initiated only if the customer has actively consented on this landing page; otherwise treat it as customer-initiated so the issuer sees a cardholder-present signal and is less likely to decline the very first invoice of the reactivated subscription.

async function reactivate(customerId: string, newPaymentMethodId: string, priceId: string) {
  // ✅ create a NEW subscription; do not revive the canceled one
  return billing.createSubscription({
    customerId, priceId, paymentMethodId: newPaymentMethodId,
    metadata: { origin: 'win_back' },
  });
}

3. Create a new subscription, not a revival

Because canceled is terminal in the FSM, reactivation is a new contract with its own anchor, its own first invoice, and a link back to the churned one for cohort analysis.

The billing-anchor decision inside this new contract has real consequences that are easy to get backwards. A reactivated subscription should anchor on the reactivation date, not inherit the old subscription’s original anchor. If you carry the old anchor forward, the new subscription’s first period may be prorated down to a stub — a customer who reactivates on the 28th against an anchor of the 1st gets billed for three days, collects almost nothing, and then renews at full price, which reads to the customer like an inconsistent charge and to your MRR reporting like a phantom expansion-then-contraction. Anchoring on the reactivation date gives a clean full first period, a predictable renewal date, and a first invoice_id whose amount matches the plan’s list price (less any bounded win-back credit). The link row in win_back_links is what keeps the two subscriptions joined for analysis without physically merging them: cohort queries can still answer “what fraction of dunning_exhausted churns from March came back within 60 days” by joining the old and new subscription_id, while each row remains an honest, standalone record of one contract.

Preserving entitlements without reopening the contract

A subtle expectation gap opens between the ledger and the product. The customer thinks they are “turning their account back on,” but the ledger has quite correctly issued a brand-new subscription. Bridge that gap in the provisioning layer, not the billing layer: on the win_back create, re-grant the customer’s prior entitlements — seat count, feature flags, retained data — keyed on the stable customer_id that survived the churn, so the account feels continuous even though the subscription_id is new. This is exactly why the customer record must outlive the subscription: workspaces, uploaded content, and API keys hang off customer_id, never off subscription_id, so a terminal subscription can close cleanly while the account it belonged to waits, dormant but intact, for a win-back that may never come. Resist the temptation to short-circuit this by reopening the old contract; the moment you mutate a canceled row you have traded a small provisioning join for a corrupted, non-reproducible revenue history.

def on_win_back(old_sub_id: str, new_sub_id: str, db) -> None:
    db.execute(
        "INSERT INTO win_back_links (old_subscription_id, new_subscription_id, won_back_at) "
        "VALUES (%s, %s, now())",
        (old_sub_id, new_sub_id),
    )   # ✅ traceable both ways; old row stays immutable and canceled

Verification & Testing

The tests prove targeting, the new-subscription rule, and measurement. Assert the campaign selects only dunning_exhausted churn, never voluntary cancels. Assert reactivation creates a new subscription row and leaves the canceled one untouched. Assert the win-back link is written both ways for cohort reporting. The panel lists them.

Win-back tests The campaign targets involuntary churn only, reactivation creates a new subscription, the old row stays canceled, and win-back rate is measured by cause. Targeting involuntary only no voluntary New sub fresh row not revived Old row stays canceled immutable Measure by cause + offer win-back rate
The new-subscription test protects revenue history — reviving a canceled contract would corrupt the ledger's immutability.

Gotchas & Production Pitfalls

The pitfalls are targeting voluntary churn, reviving the closed subscription, retrying the dead token, and not measuring by cause. The map groups them.

Win-back pitfalls Targeting voluntary churn annoys, reviving the closed subscription corrupts history, retrying the dead token fails, and not measuring by cause hides what works. Voluntary targeted too → involuntary only Revive reopen canceled → new subscription Dead token retry same card → re-collect No measure flat win-back rate → segment by cause
Four pitfalls — reviving a canceled subscription is the one that quietly breaks the immutable revenue record.
  • Targeting voluntary churn. A customer who deliberately canceled does not want a “your payment failed” win-back. Segment on dunning_exhausted and leave voluntary cancels to a different, softer campaign.
  • Reviving the closed subscription. The FSM has no edge out of canceled for a reason — reopening it corrupts revenue history. Create a new subscription and link it to the old one.
  • Retrying the dead token. The card that failed dunning is still dead. Re-collect a fresh payment method; do not attempt the stored token again.
  • Not measuring by cause and offer. A flat win-back rate hides which offer and timing work. Segment recovery by churn cause and offer so you can tune the sequence.

A subtler pitfall is treating the win-back as a marketing problem when it is really a data-integrity problem. The moment a recovered customer’s new subscription_id is not linked to the old one, three downstream systems quietly break: cohort retention reports double-count the customer as both churned and new, lifetime-value math attributes their history to the wrong account, and support sees two unrelated subscriptions for one person. Store an explicit reactivated_from reference on the new subscription pointing at the canceled one, and expose it in the read models finance and support use, so the win-back is legible as a continuation rather than a coincidence. The revenue history stays clean — the old subscription remains terminally canceled with its true end date — while analytics can still stitch the two into one customer journey.

There is also a consent and deliverability dimension that engineers tend to miss. A win-back email goes to someone whose billing relationship just ended, which in some jurisdictions changes the lawful basis for contacting them, and always changes how a spam filter reads the message. Route win-back mail through the same reputation-warmed sending domain as your transactional dunning, honor unsubscribe state before the first send, and suppress anyone who marked a prior message as spam — because a win-back campaign that damages sender reputation costs you deliverability on the invoices and receipts that fund the business, a far larger loss than the marginal subscription it was chasing. Measure the campaign’s complaint rate as carefully as its recovery rate; the two together, not recovery alone, decide whether the sequence is net positive.

Frequently Asked Questions

How long after churn is win-back still worth attempting? Meaningful recovery continues for months, with the strongest response in the first few weeks. The cost of a message is low enough that a quarterly attempt for a year is usually justified.

Should a win-back offer include a discount? Not initially. Many involuntarily churned customers left because of a card problem, not a price objection, and offering a discount first gives away margin to people who would have returned at full price.

Does the data need to be preserved for win-back to work? It is the single biggest factor. A returning customer who finds their workspace intact reactivates in one click; one who must start over usually does not return at all.

How is win-back measured? As reactivations attributable to the flow, at ninety days, against a holdout that received nothing. Counting all reactivations as wins overstates the flow’s effect substantially.