Supported subscriptions
Reference page. Use this to look up which subscription shapes Unchurn handles automatically, which ones route to a manual cancellation request, and why.
For the rationale behind the rules, see Unusual subscriptions. This page lists the rules without re-explaining them.
What gets checked
Every cancel session is checked when the flow opens and again when we make the change to Stripe. The result is one of three states.
| State | What happens | Customer sees |
|---|---|---|
| Eligible for one or more retention offers | Widget renders the eligible offers in waterfall order. Customer picks one (or declines and cancels). | Discount, pause, plan-switch, trial extension tiles — only the ones that are safe for this sub. |
| Not eligible for offers, eligible for automated cancel | Widget skips the offer waterfall and shows the cancel confirmation. | ”Subscription will end on <date>.” |
| Not eligible for automated cancel | Widget records a manual cancellation request, notifies you, and shows an honest confirmation. | ”Your cancellation request has been received.” Never “your subscription has been canceled.” |
The third state is the one most cancel-flow tools get wrong. See Manual cancellation request below.
Where to look
- Per-offer eligibility — one table per offer (cancel, discount, pause, plan-switch, trial extension). Each table lists the conditions a subscription must satisfy.
- Blocked subscription shapes — the full list of subscription shapes blocked from automated cancel, with the Stripe-API reason for each.
Manual cancellation request
When a subscription shape isn’t safe for automated cancel, the cancel button doesn’t call Stripe. Instead we record a manual cancellation request in a single database transaction.
What the customer sees
- The cancel button in the widget.
- A confirmation screen: “Your cancellation request has been received.” The screen may include your
support_urlas an optional contact link. - A confirmation email (sent immediately, retried in the background until delivered).
The screen never says “your subscription is canceled” for blocked shapes. The copy stays honest about what actually happened.
What you receive
- A merchant dashboard task with the subscription ID, customer ID, the reason the automated path was blocked, and the timestamp.
- An audit timestamp (
merchant_manual_cancellation_notified_at) stamped in the same transaction as the request, queryable via your dashboard. Outbound webhook delivery to your server is on the roadmap. - The session row records
outcome='manual_cancellation_requested',clicked_to_cancel=true, and the deterministicmanual_cancellation_request_id.
Double-clicks and retries collapse to one request via ON CONFLICT (id) DO NOTHING on the deterministic id.
What does not happen
- No
subscriptions.updatecall to Stripe. - No standard
onCompleteevent. - No
cancel_scheduledoutcome. - The session is not counted as “saved” or “compliance-success-automated” in your analytics.
You complete the Stripe-side cancellation manually when you’re ready. Until then, the customer’s intent is captured and dated.
How this satisfies FTC Click-to-Cancel
The FTC’s click-to-cancel principle requires that cancellation be processed immediately upon request — not that access be revoked immediately. The customer paid for the period; cutting access mid-period would harm the customer experience and is not what the rules require.
| Path | What “immediate” means |
|---|---|
| Automated cancel | subscriptions.update({ cancel_at_period_end: true }) is called the moment the customer clicks. Stripe records the cancellation immediately. The customer keeps access through the paid period. |
| Manual cancellation request | The request is recorded in the database in the same transaction as the merchant notification and customer-confirmation outbox, the moment the customer clicks. The merchant completes the Stripe-side cancellation within their legal SLA. |
Both paths are one click from the cancel-flow open. No surveys, no offers, and no “are you sure” interstitials sit between the customer and the cancel action when direct cancellation access is rendered.
For blocked shapes the position is received, not completed — manual cancellation requests are tracked separately from automated cancels in your analytics.
Stripe-side primitives
The five offers map to these Stripe calls. Full per-offer eligibility on the per-offer page.
| Offer | Stripe call | Cadence | Stripe docs |
|---|---|---|---|
| Cancel | subscriptions.update({ cancel_at_period_end: true }) | Any | subscriptions overview |
| Discount | coupons.create then subscriptions.update({ discounts: [{ coupon }] }) | Any | coupons |
| Pause | subscriptions.update({ pause_collection: { behavior: 'void', resumes_at } }) | Monthly only | pause-payment |
| Plan-switch | subscriptions.update({ items: [{ id, price, quantity }], proration_behavior: 'none' }) | Monthly only, downgrades only | billing-cycle |
| Trial extension | subscriptions.update({ trial_end, proration_behavior: 'none' }) | Trialing subs only | billing-cycle |
The pause call uses the collection-level pause_collection, not Stripe’s first-class status='paused' API. The two are separate models with separate resume paths.
Roadmap
Annual pause, broader plan-switch transitions, discount stacking, and schedule-backed cancel are on the roadmap. No dates. The supported set today is what the per-offer page and blocked shapes page describe.