How the cancel flow works
A cancel session moves through five phases. Knowing them helps you reason about edge cases, debug failures, and pick which events to log on your side.
The five phases
1. Token mint (your server)
Your server is the only place that knows which Stripe subscription belongs to the logged-in customer. When the customer clicks cancel, your server creates a short-lived signed token. The token carries three claims:
merchantId— your Unchurn merchant IDsubscriptionId— the Stripe sub the customer ownsmode—testorlive
The token expires in 5 minutes by default and is signed with UNCHURN_SECRET. Anyone who captures a token can replay it until it expires. Keep TTLs short, and rotate the secret from your dashboard if a token leaks.
2. Session create (Unchurn backend)
The widget sends the token to Unchurn’s backend, which:
- Checks the HMAC signature against your merchant secret.
- Reads the subscription from Stripe in your account (via your Stripe Connect link).
- Runs eligibility checks for each offer. A subscription that can be canceled may still be a poor fit for pause if it bills yearly. See Unusual subscriptions.
- Records the session in your dashboard, even when no offers fit.
If the subscription shape isn’t safe for an automated cancel, the session opens in manual mode. The cancel button files a manual cancellation request and notifies you, instead of pretending Stripe was changed.
3. Offer waterfall (in the customer’s browser)
The widget shows only the offers that fit. The customer can accept an offer (which triggers a Stripe change) or decline through to the next step. The typical order is discount → pause → plan downgrade → confirm cancel.
We log every decline and every acceptance. Your dashboard shows the full path the customer took — not only where they ended up.
4. Stripe mutation (Unchurn backend)
When the customer accepts an offer or confirms cancellation, Unchurn calls Stripe. Never the browser, never your server. The change is safe to retry, and we read the result back from Stripe before marking the session complete. If Stripe rejects the change, the session is left in a clear error state so it can be replayed by hand.
For each offer, the underlying Stripe call is documented in The five offers.
5. Confirmation
The customer sees a confirmation screen tailored to the outcome. The widget shows it directly — no round-trip to your server is needed.
When the session goes to a manual cancellation request (because the subscription shape isn’t safe to change automatically), Unchurn sends a webhook to your notification endpoint. Your team finishes the cancellation in Stripe directly. Outcome webhooks for retention events (offer accepted, cancel scheduled) are on the roadmap. Until then, your dashboard is the source of truth for retention analytics.
What does not happen
- The browser never talks to Stripe directly. Every Stripe call comes from Unchurn’s backend.
- Your server never talks to Stripe in the cancel flow. Your server only mints the token. Unchurn handles the rest via Stripe Connect.
- No customer can start a flow for a subscription they don’t own when HMAC tokens are enforced. The merchantId and subscriptionId are bound at sign time and checked on the server before any Stripe read.
Failure modes
| When | What happens |
|---|---|
| Token expired | Widget retries via /api/unchurn/token, gets a fresh one |
| Token signature invalid | Backend returns 401, widget shows a generic error, no Stripe call |
| Subscription unsafe for automated cancel | Session opens in manual mode; cancel CTA files a request instead |
| Stripe API error during mutation | Session marked errored; shown in your dashboard |
| Customer closes the widget mid-flow | Session marked abandoned; shown in your dashboard |