Docs
The reference manual. Two installs to activate, then everything else explained.
Getting started
ChurnNote is the reason-first retention OS for Stripe and Lemon Squeezy SaaS. Find the leak, save who you can, learn why they left, win them back when something changes.
When you're logged in, your live setup status appears below. Most setup is automatic once you connect Stripe or Lemon Squeezy — the only two things that need your hands are installing Cancel Flow and verifying your sender.
What needs your hands
- Install Cancel Flow — one backend call into your cancel button. Save 20–40% of cancel attempts. Setup ↓
- Verify your sender — start on our default sender or upgrade to your own domain or SMTP. Setup ↓
What runs automatically
- Failed payment recovery — auto-fires the moment a webhook arrives. How it works ↓
- AI reply intelligence — every cancellation reply and cancel-flow feedback runs through the structured extractor. How it works ↓
- Inbound reply capture — replies to founder-style emails land back in the dashboard via Resend inbound. Works on default sender, custom domain, and custom SMTP.
- Webhook ingestion — cancellations, scheduled cancels, failed payments, recoveries. Events ↓
Tools you reach for as needed
- Churn Leak Report — run it any time to see 30 days of leaking MRR. Lives at /tools/churn-leak-score.
- Requests — what customers asked for, clustered. Decide what to build. More ↓
- Segments — cancellations grouped by reason. Act on a whole reason at once. More ↓
- Product Changes — record what you shipped. ChurnNote auto-matches the exact cancelled customers who asked for it. More ↓
- Saves — recovered money plus the queue of win-back drafts ready to review and send. More ↓
How the win-back loop fits together
Three pages all touch win-backs, each answering a different question. Use them in this order:
- Requests tells you what to build. Customers cluster around the same asks. You acknowledge them so they know they were heard, then you ship.
- Product Changes tells you who to email when you ship. You record "API access shipped" with keywords; ChurnNote finds the exact people who mentioned it.
- Segments tells you who to email when the fix is reason-wide, not feature-specific. "We lowered our starter plan" goes to everyone in the pricing segment.
All three feed the same Win-backs queue. You always review and send.
Connect billing
Stripe
Create a restricted API key in your Stripe dashboard with read access to customers, subscriptions, invoices, charges, and payment intents. Paste it into ChurnNote. We never store your secret in plain text — the key is encrypted with AES-256-GCM at rest, and we read it back only to call the Stripe API on your behalf.
You can revoke the key at any time from Stripe and the connection will go inactive instantly. Both test-mode and live-mode keys work. Test mode is the safest place to play with Cancel Flow before opening to real customers.
Lemon Squeezy
Click Connect Lemon Squeezyon the onboarding screen. You'll be prompted for your API key and store ID. We register webhooks automatically so cancellations and failed payments flow into ChurnNote within seconds of the event.
What works for Lemon Squeezy today: reason collection, AI reply intelligence, billing-help link, failed-payment recovery scan, product-change win-backs, native cancel-at-period-end. What soft-captures: pause, discount, downgrade (the LS APIs for these are coming later). See Lemon Squeezy specifics for full detail.
Switching businesses
ChurnNote allows one active connection per account. You can disconnect from Settings at any time and connect a different store; previous data stays archived and visible.
Cancel Flow
When a customer clicks Cancel subscription in your app, you get one last chance. Cancel Flow shows them a reason picker, a tailored save offer (pause, discount, or downgrade), then a confirm step. Most teams running it recover 20–40% of cancel attempts in the first month.
Install (5 minutes)
From Settings → Cancel Flow, click Generate API key. The key is shown once — copy it, store it as CHURNNOTE_CANCEL_FLOW_KEY in your server environment. We only store the hash; if you lose it, regenerate (which invalidates the old key).
Wire one backend call into your cancel button. When the customer clicks Cancel:
const res = await fetch("https://www.churnnote.com/api/cancel-sessions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.CHURNNOTE_CANCEL_FLOW_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
provider: "stripe",
provider_subscription_id: "sub_123",
customer_email: "customer@example.com"
})
});
const { cancel_url } = await res.json();
return redirect(cancel_url);We return a cancel_url — a short-lived link to a hosted page that walks the customer through reasons, offers, and confirmation. Redirect them there.
Don't have a developer in the chair? In Settings → Cancel Flow, click Send to developer to copy a self-contained handoff message including the snippet, ready to paste into Slack or email.
Configure offers
Each cancel reason maps to a chain of save offers. Defaults work for most SaaS:
- Too expensive → discount, fallback downgrade
- Missing feature → feature request (notify-when-shipped), fallback pause
- Not using enough → pause, fallback downgrade
- Too complex → setup help, fallback founder follow-up
- Switching to a competitor → founder follow-up, fallback discount
- Payment issue → billing help (payment update link)
- Something else → straight to confirm
Live mode safety
Live Stripe actions are on by default.The first time you generate a Cancel Flow API key against a live Stripe connection, we flip both the master switch and the live-actions toggle on for you — that's the whole point of generating a key. ChurnNote will apply real coupons, pause real subscriptions, and schedule real cancel-at-period-end on Stripe.
If you'd rather soft-capture (record what the customer accepted and email you to follow up by hand without touching Stripe), turn off Live Stripe actions in Settings → Cancel Flow.
Always set a fallback cancellation URL (your Stripe billing portal, Lemon Squeezy store URL, or support page) so a customer who declines all offers and clicks I still want to cancel has a clean exit. We never trap customers inside ChurnNote.
Failed payment recovery
When a payment fails (card expired, insufficient funds, network decline), Stripe or Lemon Squeezy fires a webhook. ChurnNote receives it, queues a 3-email recovery sequence over 10 days, and sends each email with a fresh, single-use payment-update link.
The links don't require login. The customer clicks, updates their card directly with Stripe or Lemon Squeezy, and their subscription reactivates. Recovery is auto-on once your connection is active.
From /recovery you can see every failed payment, the recovery status (sent, recovered, expired), and manually resend or refresh a link if a customer asks.
Stripe Smart Retries
ChurnNote is Smart-Retries-aware. We wait for Stripe's built-in retry schedule to complete before sending our emails so customers don't get duplicate messages.
Email sending
Three sending modes, listed from fastest to most professional. You can always start with the default and upgrade later — past sends keep working either way.
- Default sender (instant) — sends from
your-name@mail.churnnote.comwith your founder name in the From line. Zero setup. Good enough to start. - Custom domain (10 min) — verify a domain with DNS records, send as
founder@yourdomain.comthrough our shared Resend infrastructure. Best deliverability + your brand without leaving ChurnNote. - Custom SMTP (5 min) — bring your own SMTP host. Emails leave from your own mail account, full control over deliverability, and every send shows up in your own sent folder if your provider supports it.
Every template is plain text and short. No HTML, no logos, no marketing chrome. The point is to look like a founder writing a real email, because that's what gets 10–30% reply rates instead of the 1–2% surveys get.
Configure from /settings/email.
Set up custom SMTP — send from your own mail
Custom SMTP routes every ChurnNote email through your own provider, using credentials you control. From your customer's inbox, the email looks exactly like one you wrote and sent by hand from your founder address.
Reply capture still works
This is the question everyone asks. Yes — even on custom SMTP, customer replies still land in ChurnNote. We set the Reply-To header on every outgoing email to your-name@mail.churnnote.com regardless of where the message originated. Mail clients honor Reply-To when the customer hits reply, so the reply routes to our inbound parser. You read it in the dashboard, the AI extracts the reason, and the loop closes.
Your From address looks like you. Your Reply-Tois ChurnNote. Customers don't notice; you don't lose replies.
Step 1 — Pick a provider and get SMTP credentials
Any SMTP server works. Common picks for SaaS founders, with the exact values you'll need to paste into ChurnNote:
| Provider | Host | Port | Security | Username | Password |
|---|---|---|---|---|---|
| Resend | smtp.resend.com | 465 | SSL | resend | Your Resend API key |
| Postmark | smtp.postmarkapp.com | 587 | STARTTLS | Server API token | Same as username |
| SendGrid | smtp.sendgrid.net | 587 | STARTTLS | apikey | Your SendGrid API key |
| Amazon SES | email-smtp.<region>.amazonaws.com | 587 | STARTTLS | SES SMTP username | SES SMTP password |
| Mailgun | smtp.mailgun.org | 587 | STARTTLS | postmaster@your-domain | Your SMTP password |
| Gmail (Google Workspace) | smtp.gmail.com | 465 | SSL | Your Gmail address | App password (not your login) |
For Gmail you must use a 16-character app password, not your real password. Generate one at myaccount.google.com/apppasswords — your Workspace admin needs to allow it.
Step 2 — Verify your sender domain with the provider
Before you can send from founder@yourdomain.com, the provider needs to know you own that domain. Each provider walks you through adding DNS records (SPF, DKIM, sometimes DMARC). Do this in their dashboard, not ChurnNote's. Skipping verification almost always lands your emails in spam.
Quick sanity check: send a test email from the provider's own dashboard to yourself and check that it lands in the inbox, not promotions or spam. If it doesn't, fix DNS before adding credentials to ChurnNote.
Step 3 — Add credentials in ChurnNote
Go to /settings/email and switch the sending mode to Custom SMTP. Fill in:
- SMTP host — from the table above, e.g.
smtp.resend.com - Port — usually 587 (STARTTLS) or 465 (SSL)
- Security — pick SSL, STARTTLS, or None to match the port
- Username — the SMTP username for your provider (see table)
- Password — the SMTP password or API key. We encrypt this at rest with AES-256-GCM; we never log it.
- From name — how you appear in the customer's inbox (e.g. Alex from MakerStory)
- From email — the address customers see, e.g.
founder@yourdomain.com. Must match a verified sender at your provider.
Step 4 — Send a test email
ChurnNote requires a successful test before live sending. Click Send test email, enter your own address, and check the inbox.
- The test will arrive from your From email with Reply-To set to
your-name@mail.churnnote.com— that's correct. Replying to the test from your own inbox doesn't do anything; it's a self-test. - If the test passes,
smtp_verified_atis recorded and real sends start using your SMTP immediately. - If the test fails, ChurnNote shows the SMTP error verbatim. Most common causes are below.
Troubleshooting custom SMTP
- Authentication failed / 535 — wrong username or password. For Gmail, you must use an app password, not the regular one. For SendGrid the username is literally
apikey. - Connection timeout — wrong port for the chosen security. Try 587 with STARTTLS or 465 with SSL. Some hosts also block outbound port 25; that's expected.
- Sender not authorized / 550 — your provider hasn't verified the From email domain yet. Go back to Step 2.
- Emails arrive in spam — DNS records (SPF, DKIM, DMARC) aren't fully set up at the provider. Fix at the provider; ChurnNote doesn't touch DNS.
- Replies aren't in the dashboard — make sure the test email's Reply-To is the ChurnNote mail.churnnote.com address. If it's pointing at your own inbox, something is overriding our header — open the email's raw source and check the
Reply-To:line. Email us if it's wrong.
Switching back to default sender
Toggle the sending mode back to Default sender in /settings/email. Your SMTP credentials stay stored (encrypted) but ChurnNote stops using them — useful if you want to try out the default sender for a campaign without losing your saved config.
Requests
/requests answers one question: what are customers asking for? It clusters every feature, integration, and competitor name customers mentioned — across email replies, cancel-flow feedback, and AI-extracted mentions — into a single ranked list.
What you see
- Each row is one ask — e.g. "API access — 5 customers · 2 high recoverability · $240/mo lost · 3 pending ack".
- Near-duplicate wordings collapse — "need slack integration", "Slack integration", "i need slack" all merge into one row. ChurnNote fingerprints by significant tokens, not exact strings.
- Still-subscribed askers are tracked separately — customers who asked for the feature in your Cancel Flow but stayed (saved by an offer) appear under the same ask with a "subscribed" tag.
Two actions per ask
- Acknowledge — sends the AI-drafted "I heard you" reply to every cancelled customer who mentioned this. Reuses your normal reply pipeline so it lands in the same email thread.
- Notify subscribers — for still-subscribed askers, sends a "we shipped it" email after you build the thing. Triggered manually, never auto.
When to use Requests
Use Requests before you ship. It tells you what to build next based on what people actually said as they left. Once you ship the thing, move to Product Changes to win back the cancelled customers who specifically asked for it.
Recency window
Defaults to the last 90 days so the list stays relevant. Switch to "All time" to see older asks.
Segments
/segments answers a different question: why are people leaving, and what can I do about a whole group at once? Every cancelled customer with a reason signal — from email categorization, AI reply intelligence, or Cancel Flow — gets grouped into a reason segment.
The reason taxonomy
- Pricing — too expensive, can't afford, competitor cheaper, low value
- Missing feature — core feature missing, integration missing, workflow mismatch
- Bad experience — bugs, bad support, reliability
- Too complex, Not using, Switched tool, Business change, Consolidating, Temporary pause, Security/compliance, Other
Signal filters
On top of reason grouping, three filters narrow down to the most actionable subset:
- Likely to come back — high recoverability, positive sentiment, or comeback language in their reply
- High LTV — customers worth the most effort
- Early churn — cancelled within their first months
The Segments win-back flow
- Pick a reason (e.g. Missing feature → Integration missing).
- Select customers.
- Type a free-text "What changed for them?" — e.g. "We added the HubSpot integration."
- ChurnNote drafts one email for the group, classifies each customer as eligible-to-send-now or to-be-scheduled, and previews the recipient list.
- Review the draft inline, edit if needed, then send the eligible ones now and queue the rest (or queue everyone for manual review).
When to use Segments vs Product Changes
Use Segmentswhen the fix applies to a whole reason and there's no specific keyword to match on. Examples: "we lowered our starter plan" for pricing, "we rebuilt onboarding" for too_complex, "we're running a Black Friday offer" for any group.
Use Product Changes when you shipped a specific named thing and want only the customers who mentioned it. Segments is the broad brush; Product Changes is the precision instrument.
Product Changes
/product-changesis the connective tissue between "we just shipped X" and "here are the exact customers who left because X was missing." You record what you shipped with a few keywords. ChurnNote auto-matches every past cancellation that mentioned those keywords, ranks them by recoverability, and prepares targeted win-back drafts.
How matching works
When you record a change with keywords like api, api access, rest api, ChurnNote searches every cancellation in your active connection for those terms across four fields:
intel_mention— the single normalized feature/competitor name the AI extracted from their replyfeature_requests— the array of free-text labels the email categorizer extractedreply_body— what the customer wrote in their cancel replycancel_flow_feedback— what they typed into your hosted cancel page
Matching is case-insensitive substring. Rows are excluded if you've archived them or marked them unrecoverable. Optional churn-reason filternarrows further — set it to "missing feature" for a shipped feature so you don't catch unrelated pricing complaints that happened to mention "API".
Recording a change
- Open /product-changes, click Record a change.
- Title — e.g. "API access shipped". Used as the subject hint in the AI draft.
- Keywords — comma-separated. Add the variants people actually used:
api, api access, rest api, webhooks. Two characters minimum each, max 20 keywords. - Details (optional) — one sentence on what shipped. Passed into the AI draft as the "what changed" context.
- Churn reason filter (optional) — restrict matches to one category. Recommended for shipped features (set to Missing feature).
- Shipped on — defaults to today. Used for sorting and history.
What you see after saving
The row updates live with two numbers:
- Matched customers — total people who mentioned any of your keywords
- High recoverability — subset the AI tagged as high-likelihood-to-return
Click the row to expand the full match list. Each row shows the customer, the keywords that matched (as chips), the recoverability tag, plan MRR, time since cancellation, and a preview of their feedback. Customers who already have an active or sent win-back are dimmed and locked from selection so you never double-email.
Queuing the win-back
- Select customers (or select all).
- Click Preview & queue.
- ChurnNote drafts ONE email (subject + body) using the change title, details, and the dominant churn reason in the group. Sender identity comes from your active connection.
- The modal classifies recipients:
- Eligible — access already ended, can send now
- Scheduled — access still active, will queue for 24h after access ends
- Skipped — already have an active win-back from a previous flow
- Edit subject/body inline.
{first_name}auto-replaces per customer. - Choose "Send N now + queue M" or "Queue all". Sent emails go via your active sender; queued ones become Ready/Scheduled rows in /winbacks.
How it's different from Segments
- Product Changes is feature-first. You start from what you shipped and pull the matching customers automatically.
- Segments is reason-first. You start from a reason category and pick customers manually after typing what changed.
- Both end up as rows in the same Win-backs queue. Product Changes additionally tags each row with
product_change_idso you can answer "how much MRR did the API ship recover?" in History.
Editing or archiving a change
Each row has an Edit button. Updating keywords re-runs matching live (the row's count updates on save). Archiving hides it from the list but keeps the win-back history intact so the audit trail in /history stays accurate.
Good keyword hygiene
- Use lowercase. Matching is case-insensitive but lowercase keeps the chips clean.
- Use the variants customers actually wrote. If your AI extracted "slack integration" in some replies and "slack" in others, list both.
- Two-word phrases are usually safer than one-word for common terms. "api" alone can over-match (e.g. "capable"). Prefer "api access" or "rest api".
- Don't over-stuff. Three to six tight keywords beat fifteen noisy ones.
- Use the churn-reason filter for shipped features. It dramatically cuts false positives.
Win-backs
Win-backs is the queue. Drafts land here from three places (Segments, Product Changes, and per-customer scheduling on a cancellation detail page), and they all behave the same way once they're in the queue. You review, you edit, you send. We never auto-send win-backs — founders win back customers, software doesn't.
What the queue shows
- Ready — customer access already ended; the email can go right now.
- Scheduled — customer is still on access (period_ends_at in the future). The email will be eligible to send 24 hours after their access ends. ChurnNote does not auto-send; it just keeps the draft warm.
- Sent — outbound recorded, replies route back to the same thread via Resend inbound capture.
- Won back — they re-subscribed after the email. Marked manually or detected via webhook.
Smart send timing
When you queue a win-back for someone whose access hasn't ended yet, ChurnNote auto-schedules it for access_ends_at + 24 hours. Reason: emailing "come back" while the customer can still log in feels off. Once access ends, the offer reads as a genuine second chance.
How drafts get personalized
Batch flows (Segments and Product Changes) generate one AI template, then swap {first_name}per customer. Per-customer flows (from a cancellation detail page) generate a fully bespoke draft using the customer's reply, recoverability, LTV, and the product change note you typed.
From /winbacks you'll see drafts ready to send, drafts scheduled for the future, and historical sends with reply tracking. /history shows the full audit trail and which Product Change (if any) spawned each win-back.
AI reply intelligence
Every cancellation reply and every cancel-flow feedback text runs through a structured extractor that returns:
- Primary reason — pricing, missing feature, competitor, etc. (matches dashboard taxonomy)
- Sub-reason — a plain-English 8-word phrase, e.g. "Onboarding felt confusing"
- Feature mentioned — specific product feature the customer wanted (or null)
- Competitor mentioned — tool they're switching to (or null)
- Sentiment — positive, neutral, frustrated
- Recoverability — low, medium, high
- Suggested action — one imperative sentence
- Drafted reply — 3–5 sentence plain-text founder-style email, ready to copy
You see the extracted fields on every cancellation detail. Re-run the extractor any time from the same screen.
Lemon Squeezy specifics
ChurnNote is multi-provider by design. Here's what works for Lemon Squeezy today vs. coming later:
Works today
- Cancel Flow hosted page (reasons, feedback, soft-capture save offers, native cancel-at-period-end)
- AI reply intelligence on cancel-flow feedback and email replies
- Billing help (returns the LS payment update URL)
- Failed-payment recovery scan and email sequence
- Product-change win-backs
- Founder email notifications
- Inbound reply capture
- Native cancel-at-period-end via
DELETE /v1/subscriptions/:id
Coming later
- Real LS pause (no native pause API — would be implemented via "switch to free variant")
- Real LS discount application (LS coupons API not yet wired)
- Real LS downgrade (variant swap not yet wired)
Until those land, pause/discount/downgrade soft-capture: the customer expresses interest, the founder gets notified, the customer is routed to the fallback cancellation URL if they still want to leave. No false promises.
Webhooks & events
ChurnNote registers webhooks automatically when you connect a provider. You don't need to configure them manually.
Stripe events ingested
customer.subscription.deleted— cancellationcustomer.subscription.updated— scheduled cancel, plan changesinvoice.payment_failed— triggers failed-payment recoveryinvoice.payment_succeeded— confirms recoverycharge.dispute.created— dispute signal
Lemon Squeezy events ingested
subscription_cancelledsubscription_updatedsubscription_payment_failedsubscription_payment_recovered
Reply capture is handled separately via Resend inbound webhooks. Customer replies to founder-style emails land back in the dashboard within seconds.
Slack notifications
Real-time pings when Radar-class signals fire. Off by default. Email and the weekly digest stay on regardless of whether you enable Slack.
What fires a Slack ping
- Scheduled cancellation — a customer just clicked Cancel and the subscription is set to end at period close. Fires once per cancellation.
- Payment failed — a Stripe invoice failed and ChurnNote opened a recovery sequence. Fires on the first failure for that invoice.
Abandoned cancel-flow and high-recoverability churn show on the Radar page but don't ping Slack in v1 (they're derived signals, not webhook events).
Setup
- Go to Slack incoming webhooks and create one pointed at the channel you want pings in.
- Copy the URL (starts with
https://hooks.slack.com/services/...). - Paste it into Radar → Setup → Slack notifications and click Save.
- Click Send test ping to confirm it arrives in the right channel.
- Flip Notify on Radar signals on. From now on, scheduled cancels and failed payments fire a Slack message in real time.
ChurnNote validates the URL must be on hooks.slack.comover https. Anything else is rejected at save time so a typo can't leak data to the wrong endpoint.
Churn Radar
Radar surfaces customers at risk of churning so you can act before the cancel button gets clicked. Each signal is computed from existing data — most need no setup beyond connecting your billing provider. The Inactivity-class signals additionally need the activity pixel.
Radar is strictly forward-looking: it shows still-subscribed customers showing risk so you can act before they cancel. Customers who've already cancelled live on /dashboard, where high-recoverability ones get a green RECOVERABLE badge so they stand out in the cancellation list.
Today Radar runs on five signals. Three more are planned and require infrastructure that isn't shipped yet — they're honestly listed so you know what's coming.
Active signals (still-subscribed customers at risk)
- Scheduled cancellation (next 14 days) — customers whose subscription is set to cancel at period end. Reach out before the sub actually ends.
- Abandoned cancel flow — customers who started cancel flow, picked a reason, then walked away mid-flow. Real intent signal, often saveable.
- Failed payment, not recovered — recovery sequence is firing on real failed payments. Watch for lapses.
- Drop in usage — previously engaged customers (3+ sessions, 7+ days tenure) who've gone quiet 5-13 days. Early warning before they hit Inactive.
- Inactive customers (14+ days) — customers your app hasn't seen in two weeks. Lapsing usage usually precedes cancellation.
Already cancelled? On the Dashboard.
When a customer cancels and the AI tags them as high recoverability, they show up on /dashboard with a bold green RECOVERABLE pill on their cancellation row. The pill drops when you click Acknowledge in Requests, mark them unrecoverable, or they reply on their own — same handling rules as before. Slack pings (if enabled) still fire in real time the moment the AI tags a fresh cancellation.
Planned signals
These are real product roadmap items, not aspirational fluff. They're held until the infrastructure they need exists.
- Trial signals — customers stuck in onboarding or running out of trial without engagement. Needs: ChurnNote ingesting Stripe subscriptions with
status=trialing+trial_end, which is a separate sync we don't do today (we currently only ingest cancellations + failed payments). Half-day of build when prioritized. - Per-feature inactivity — customers who stopped using your key feature even though they're still logging in. Needs: the activity pixel extended to send
event(not justemail), plus UI to define which features count. About a day of build. - Plan-fit mismatch — customers on an Enterprise plan whose actual usage looks like a Hobby plan, or vice versa. Needs: per-feature usage tracking + plan metadata sync. Bigger build.
Notifications
Slack pings (real-time on three signals, daily digest on the rest) are configured under Settings → Slack. Off by default; opt in by pasting a Slack incoming-webhook URL.
The weekly digest email already covers your Radar signals separately — Slack is the live channel.
Activity tracking
A lightweight timestamp ping. When the pixel runs in a customer's browser, it tells ChurnNote “this email was here at this time.” That's it — no URLs, no behaviour tracking, no session recordings. We use those timestamps to flag customers who've gone silent before they actually cancel.
Where to install it
One post-login page. Pick something every active user hits — your main dashboard, home, project list. Just one place is enough to power the Inactive customers and Drop in usage signals.
Don't put it on:
- The login page itself — they're not authenticated yet, so the email field will be empty and the ping will be skipped.
- Marketing or logged-out pages — no email to pass in.
- Every single internal page — overkill. Inflates ping counts without improving signal accuracy.
If you want more accurate “is this customer actively using my product” data, fire it on a few key feature pages too. But for the inactivity / drop signals to work, one post-login page is the floor.
What we track
- Customer email — so we can match them to their cancellation later if they churn.
- Timestamp of each ping — drives the “last active” signal.
- Count of total pings per customer — used to filter out one-and-done visitors from the Drop in usage signal (need ≥3 pings to qualify as “previously engaged”).
- User agent — captured for debugging.
What we don't track
- No URLs, page paths, or referrers.
- No click behaviour, scroll depth, or session recordings.
- No additional PII beyond the email you choose to pass in.
If you want full analytics, install Mixpanel or PostHog. ChurnNote is the minimum tracking needed to flag inactivity — nothing more.
Install
Grab the snippet pre-filled with your connection id from Settings → Activity tracking. Two versions available: vanilla JavaScript for any framework, and a React/Next.js component for direct drop-in.
Auth model
Public endpoint. No secret needed — the site key is your unguessable connection id (a UUID). Safe to ship to the browser. Spam doesn't expose data; it only bumps existing customers to more active, which silences a churn signal rather than creating false alarms.
Tuning
Default inactivity threshold is 14 days. Drop in usage fires at 5-13 days quiet for customers with ≥3 pings and ≥7 days of tenure. We skip brand-new pings (<3 days old) so the signal isn't noisy on fresh installs. Thresholds live in src/lib/radar.ts if you need to adjust them.
Troubleshooting
Cancel Flow shows "Needs fallback URL"
Live actions are on but no fallback URL is set. If a Stripe write fails mid-flow (network blip, expired card, deleted price), the customer needs somewhere safe to land. Add a fallback URL — your Stripe billing portal or support page — in Settings → Cancel Flow. Lemon Squeezy connections don't strictly need one because native cancel works without ChurnNote writes.
I generated an API key and lost it
We only store the hash. Regenerate from Settings → Cancel Flow (this invalidates the old key), update CHURNNOTE_CANCEL_FLOW_KEYon your server, and you're back in business.
Custom SMTP emails aren't arriving
ChurnNote requires a successful test email before live SMTP sending. From /settings/email, send a test to yourself. Check spam, check your SMTP logs. If the test passes, real sends will too.
Webhook says "invalid signature"
Re-register the webhook from Settings → Integrations. Most often this means the webhook secret rotated on the provider side and ours is stale.
Something else
Email hello@churnnote.com with your account email and a short description. Replies within 24 hours.
Ready to set up? Open the dashboard →