Test and live modes
Unchurn supports both Stripe test mode and live mode. The two are kept apart end-to-end — a test-mode token can’t trigger a live-mode change, and a live-mode token can’t trigger a test-mode change. The mode is set by your server when it signs the token; the widget and the browser never get to choose it.
How modes are bound
When your server signs a token, you include a mode value of test or live. The widget passes the token to Unchurn’s backend, which verifies the signature, reads the mode, and runs the rest of the session against that mode’s Stripe environment — your test secret for test, your live secret for live. Because the mode is signed into the token, the browser can’t change it on the way through.
Token prefixes
Tokens carry their mode in the prefix, mirroring Stripe’s sk_test_ / sk_live_ convention:
| Mode | Prefix |
|---|---|
test | unch_test_… |
live | unch_live_… |
That way a token in your logs is identifiable without decoding.
Setting the mode
Set the mode when you sign the token:
// app/api/unchurn/token/route.ts
import { createUnchurnHandler } from '@unchurn.dev/widget/server'
export const POST = createUnchurnHandler({
secret: process.env.UNCHURN_SECRET!,
merchantId: process.env.UNCHURN_MERCHANT_ID!,
resolveUser: async (req) => {
const user = await getCurrentUser(req)
if (!user) return null
return {
subscriptionId: user.stripeSubscriptionId,
mode: process.env.NODE_ENV === 'production' ? 'live' : 'test',
}
},
})The default if you skip mode is live. Be explicit when in doubt.
Why test and live stay separate
Stripe Connect connects test and live as two separate authorizations — connecting one doesn’t connect the other. Subscriptions on Stripe are also mode-scoped: a test-mode subscription is invisible to the live API and vice versa, even though the ID format looks identical. Unchurn keeps each mode’s Stripe connection separate and reads subscriptions against the matching mode. If the token’s mode doesn’t match the subscription you’re acting on, Unchurn rejects the request before calling Stripe and records the session as a mode mismatch in your dashboard.
What test mode does
Test mode runs the full cancel flow UI against real Stripe test-mode subscriptions and makes real changes against your test environment — scheduled cancels, pauses, discounts, the works. The widget header shows a visible TEST badge so your team and any test users can tell at a glance they’re in a rehearsal session.
Sessions are recorded in your dashboard with a test flag.
What test mode doesn’t do: touch any production Stripe data, ever.
Mismatched mode and Stripe key
If your token says mode: 'test' but only your live Stripe connection is set up (or vice versa), the session fails when Unchurn tries to read the subscription. Check Account → Stripe connection in the dashboard — both modes are connected independently, and a test subscription ID will only resolve against a connected test mode, not against live.
Recommended setup
- Local development. Always test mode, against a Stripe test customer with a test subscription.
- Staging / preview environments. Test mode, against the same test merchant or a separate test-only merchant if you want isolation.
- Production. Live mode, against the real customer’s live subscription.
The mode is set by your server’s environment. If you’re tempted to add a query parameter to switch modes from the browser, don’t — that defeats the whole isolation guarantee.