Add churn prevention to your subscription app.
Connect Stripe, install the React SDK, launch adaptive cancel flows, recover failed payments, and send the customer signals Churnless needs to protect MRR.
Create keys
Use a browser-safe publishable key in your app and a secret key only from your server.
Connect Stripe
Paste a restricted Stripe key so Churnless can read products, customers, subscriptions, invoices, and payment failures.
Install the SDK
Wrap your app with the provider, then drop the cancel flow where cancellation starts.
Send signals
Track usage, feedback, support, and lifecycle events so the save path can adapt to the customer.
Create the right keys.
Churnless uses two key types. The publishable key can run in the browser. The secret key is only for server-side API calls, MCP, jobs, and customer imports.
Publishable key
Use `fk_pub_...` with the React SDK and pixel. It can start cancel flows and check payment state for known customers.
Secret key
Use `fk_live_...` only on your server. Never expose it to client bundles, mobile apps, or public config.
Stripe restricted key
In Churnless settings, paste a restricted Stripe key with the prefilled least-privilege permissions. Churnless uses read scopes to import customers, products, prices, subscriptions, invoices, and charges, plus limited write scopes for save actions such as pauses, discounts, and cancellation at period end. Start in test mode, run the import, then switch to your live Stripe key when the mapping looks right.
Install the React SDK.
The SDK handles API calls, modal state, lifecycle events, and optional pixel forwarding. Use it anywhere your customer can manage billing.
npm install @furnkey/reactimport { ChurnlessProvider } from "@furnkey/react";
export function AppProviders({ children, customer }) {
return (
<ChurnlessProvider
publishableKey={process.env.NEXT_PUBLIC_CHURNLESS_KEY}
customerId={customer.stripeCustomerId}
productId="prod_adscreator"
trackingConsent={customer.analyticsConsent}
>
{children}
</ChurnlessProvider>
);
}Replace the raw cancel button.
Pass the same customer ID you use in Stripe. Churnless starts a session, asks for the reason, chooses the next best step, and reports the outcome.
import { CancelFlow } from "@furnkey/react";
export function BillingSettings({ customer }) {
return (
<CancelFlow
customerId={customer.stripeCustomerId}
onSaved={() => window.location.reload()}
onCanceled={() => window.location.assign("/goodbye")}
onError={(error) => console.error(error)}
>
{({ show, isLoading }) => (
<button onClick={show} disabled={isLoading}>
{isLoading ? "Loading..." : "Cancel subscription"}
</button>
)}
</CancelFlow>
);
}Block involuntary churn without blocking recovery.
Use the payment wall around authenticated app content. Customers with unresolved failures see a clear path to update payment details; everyone else sees the app.
import { PaymentRecoveryWall } from "@furnkey/react";
export function ProtectedApp({ customer, children }) {
return (
<PaymentRecoveryWall
customerId={customer.stripeCustomerId}
fallback={children}
pollInterval={60000}
/>
);
}Send the product signals that predict churn.
The better the signal history, the better the cancellation path. Track meaningful usage, activation, support friction, billing friction, and feedback.
<script>
window.fk = window.fk || function(){(window.fk.q = window.fk.q || []).push(arguments)};
fk("init", "fk_pub_...", {
customerId: "cus_123",
productId: "prod_adscreator",
consent: true
});
fk("track", "feature_used", {
feature: "campaign_export",
value: 1
});
</script>
<script async src="https://www.getchurnless.com/pixel/v1.js"></script>await fetch("https://www.getchurnless.com/api/v1/events", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.CHURNLESS_API_KEY
},
body: JSON.stringify({
customerId: "cus_123",
productId: "prod_adscreator",
eventType: "feature_used",
eventName: "campaign_exported",
category: "usage",
payload: {
exportCount: 3,
campaignId: "camp_789"
}
})
});Usage
feature_used, project_created, export_completed
Lifecycle
trial_started, activation_completed, team_invited
Friction
support_ticket_opened, error_seen, downgrade_viewed
Named product actions for session proof
For Friction Radar product issues, emit allowlisted action names so the AI PM can cite product intent (not only rage clicks). Prefer churnless_action tags in recording, or track with stable names like billing.portal_open, checkout.submit, onboarding.invite_sent, and error.payment_failed. Names must match ^[a-zA-Z][a-zA-Z0-9_.:-]{0,99}$. Settings → Products shows your instrumentation score.
Replay consented journeys without exposing sensitive data.
Session recording is available on every plan, but it is disabled for every product until you enable it in Settings → Products. The existing trackingConsent value remains the only end-user consent control.
Privacy modes
Balanced masks every form value while keeping ordinary page text for journey context. With the separate prompt-capture opt-in enabled in recording settings, recognized prompt and composer values are recorded verbatim; sensitive fields stay masked regardless.
Strict masks all form values and ordinary page text.
Both modes exclude canvas pixels, inline images, fonts, console output, and cross-origin iframe contents.
Mark sensitive regions
Add data-churnless-prompt to a prompt composer that should be readable in Balanced replay when prompt capture is opted in. Use rr-mask, rr-block, and rr-ignore, or the equivalent data-churnless-mask, data-churnless-block, and data-churnless-ignore attributes, to override capture for sensitive areas.
Sampling is deterministic per session. The recorder downloads only after product enablement, analytics consent, Do Not Track, and sampling checks pass. Replays stay private and expire after 30 days.
Use the API and MCP server for automation.
Server-side integrations can import customers, push events, inspect health scores, list cancel sessions, and let internal AI tools query churn context.