Skip to Content
Quickstart

Quickstart

Ship a cancel-flow widget in 6 lines of code. Works with Next.js App Router out of the box.

1. Install

pnpm add @unchurn.dev/widget # or: npm install @unchurn.dev/widget

2. Mint signed tokens server-side

Create one route file. This is where your merchant signing secret lives — never expose it to the browser.

// app/api/unchurn/token/route.ts import { createUnchurnHandler } from '@unchurn.dev/widget/nextjs' import { getCurrentUser } from '@/lib/auth' // your own auth helper 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 } }, })

3. Drop the widget into your app

// app/components/CancelButton.tsx 'use client' import { useUnchurn } from '@unchurn.dev/widget/react' export function CancelButton() { const { show, isReady } = useUnchurn({ tokenEndpoint: '/api/unchurn/token' }) return ( <button disabled={!isReady} onClick={show}> Cancel subscription </button> ) }

That’s it. The widget fetches a signed token from your server and handles everything else.


What this does

  • Your server minted a scoped HMAC token that looks like unch_live_…. The prefix identifies the environment at a glance, the same convention Stripe uses for sk_live_….
  • The widget called the Unchurn backend with that token; our API verified the signature against your secret before creating a session.
  • No subscription_id in a browser script can trigger a real Stripe mutation unless it carries a valid signed token.

Test mode

For development, set mode: 'test' in the handler options. Test-mode tokens are prefixed unch_test_ and can’t trigger live Stripe mutations — the backend rejects any attempt to mix the two.

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: 'test', // issue unch_test_… tokens for local dev } }, })

Environment variables

# .env.local UNCHURN_SECRET= # from your merchant dashboard (32-byte hex) UNCHURN_MERCHANT_ID=mch_…

Without Next.js

If you aren’t on App Router, you can still mint tokens server-side. The primitives under @unchurn.dev/widget/nextjsencodePayload + signPayload — are framework-agnostic and run on any Node 18+ runtime. See the auth tokens reference for the full signing protocol, token format, and error codes.


Test mode only — do not use in production: the no-auth CDN path below lets you get the widget running with a single <script> tag, no server. Any browser that guesses a subscription ID can open the flow for it. Use it only for UI experiments and demos; flip to the signed flow above before going live.

<!-- CDN demo — insecure; no HMAC verification --> <script src="https://cdn.unchurn.dev/widget.js" async></script> <script> document.getElementById('cancel-btn').addEventListener('click', () => { window.unchurn.showCancelFlow({ merchantId: 'mch_YOUR_MERCHANT_ID', subscriptionId: 'sub_STRIPE_SUB_ID', mode: 'test', }) }) </script>

Next steps

Last updated on