automations.help

Reporting & dashboards

Monitor subscription MRR and churn from Recharge

Recharge knows exactly how much recurring revenue you have and how fast subscribers are leaving — but only if you log in, and only in its own definitions. This puts the numbers where you'll see them. On a schedule it pulls your active subscriptions from Recharge, normalizes every billing interval to a monthly figure so MRR is comparable, snapshots it to a Google Sheet so you have a history the API can't reconstruct, counts the period's cancellations with their reasons, and posts MRR, net movement, and churn to Slack. Build it yourself with the steps below, or we build it on your own Recharge, Sheets, and Slack accounts, reconcile it against your dashboard, and hand you the keys so you own it with no monthly fee. Free teardown of what you check by hand first.

Difficulty
Intermediate
Time to build
3-5 hrs
Updated
  • Recharge
  • Slack
  • Google Sheets

The manual way today

Once a month — if at all — someone opens Recharge, reads the active-subscriber count and the MRR figure off the analytics page, and writes it next to last month's in a notes app or a spreadsheet. To get churn they export the subscriptions list to CSV and try to work out how many cancelled, often by eyeballing dates, and the cancellation reasons stay buried in a column nobody reads. The 'at-risk' money — subscriptions whose card just failed and are one dunning cycle from gone — never makes it into the picture at all, because that lives on a different screen.

A subscription business is a leaky bucket: every point of monthly churn is revenue you have to re-earn before you grow a dollar, and it compounds quietly because no single cancellation feels like much. The real bleed is that you can't even compute a churn rate without knowing the subscriber base at the start of the period — and nobody snapshots that, so the API only ever tells you 'now.' Meanwhile failed-payment dunning sits unwatched, so MRR you already counted slips away weeks later with no warning, and by the time a soft month shows up in the bank it's the tail end of churn that started months ago and is expensive to reverse.

What running looks like

Every period a digest lands in Slack: current MRR with the change versus last period, net MRR movement split into new, churned, and reactivated, the active-subscription and active-subscriber counts, ARPU, the subscriber and revenue churn rates measured against the real starting base, the top cancellation reasons in plain words, and the MRR currently at risk in failed charges. It's backed by an append-only Google Sheet you own — the ledger that makes 'MRR last month' a real number instead of a guess — reconciles to what Recharge's own analytics shows, and posts even on a clean week so a silent channel never gets mistaken for zero churn.

01The build

How to build it

  1. Pin the definitions before you pull a single record

    Needs a human

    Write down, on paper, what each number means — this is what lets the finished digest reconcile with Recharge instead of being a fourth figure nobody trusts. MRR: normalized monthly recurring revenue from active subscriptions only (not one-time add-ons, not last month's charges). Churn: pick subscriber churn (subscriptions or customers cancelled ÷ the base at period start) and revenue churn (churned MRR ÷ starting MRR), and decide whether you count cancelled subscriptions or cancelled customers — a customer with three boxes who drops one is contraction, not a full churn. Period: weekly or monthly, in your store's timezone. Baseline: the prior period's snapshot, which is why step 4 exists. Decide all of this now; the later steps just enforce it.

  2. Pull active subscriptions from Recharge — paginate and respect the rate limit

    Runs itself

    Call the Recharge Admin API with an HTTP Request node: GET https://api.rechargeapps.com/subscriptions?status=active&limit=250, with your X-Recharge-Access-Token header. Recharge's 2021-11 API uses cursor pagination — the response carries a next_cursor, and you follow it with ?cursor=<value>&limit=250 (once you pass a cursor, send only cursor and limit; the filters are baked into it). Loop until next_cursor is empty, or you silently cap at 250 and undercount every real catalog. Recharge rate-limits on a token bucket (roughly two requests a second on the standard plan), so a 429 means slow down, not fail — read the response and back off rather than dying. Keep price, quantity, charge_interval_frequency, order_interval_frequency, order_interval_unit, status, created_at, customer_id, and product_title.

  3. Normalize every billing interval to a monthly figure — the trap

    Runs itself

    You can't add a monthly box, a 90-day prepaid, and a weekly refill together as-is — they bill on different clocks, so MRR means nothing until each line is converted to a monthly amount. For each subscription, line MRR = price × quantity, scaled by the charge cadence: if order_interval_unit is 'month', divide by charge_interval_frequency (a quarterly $90 sub is $30/mo); if 'week', multiply by 4.3452 ÷ frequency; if 'day', multiply by 30.4375 ÷ frequency. Use charge_interval_frequency, not order_interval_frequency — on a prepaid subscription the customer is charged once for several deliveries, and MRR should follow the cash, not the boxes. Sum the line MRRs for total MRR. Count active subscriptions, and separately count distinct customer_id for active subscribers (they're different numbers, and you'll want both). ARPU = MRR ÷ active subscribers.

  4. Snapshot to Google Sheets — the history the API can't give you

    Runs itself

    This is the step that makes the whole thing possible, not an afterthought: Recharge only ever answers 'what's true right now,' so there is no API call that tells you what MRR was on the first of last month. Append one row per run to a Google Sheet via the Sheets API (append, not overwrite) — date, total MRR, active subscriptions, active subscribers, ARPU. That append-only ledger is what every rate in step 6 reads its baseline from, and it's an asset you own outright, readable without any tool. Make the append idempotent: key on the period date so a retry or a restart can't write the same period twice and quietly double your base.

  5. Pull the period's cancellations and their reasons

    Runs itself

    Churn is an event, so pull it as one. GET /subscriptions?status=cancelled with updated_at_min and updated_at_max bracketing your period as a coarse net, then filter precisely in code on cancelled_at falling inside the window — Recharge stamps cancelled_at, cancellation_reason, and cancellation_reason_comments on each one. Paginate the same cursor way. Count them, and sum each cancelled subscription's normalized MRR (same math as step 3) to get churned MRR. Tally the cancellation_reason values for the 'why' breakdown — 'too much product', 'too expensive', 'going on pause' each point at a different fix. One edge worth catching: a cancel paired with a brand-new subscription for the same customer in the same window is usually a swap or downgrade, not a lost customer, so don't let it read as full churn.

  6. Compute churn and net MRR movement against the snapshot baseline

    Runs itself

    Now the Sheet earns its keep. Read the row from the start of the period: subscriber churn rate = cancellations in the period ÷ active subscribers at period start; revenue churn rate = churned MRR ÷ MRR at period start. A rate without that denominator is just a count pretending to be a rate, which is the most common way these reports mislead. Then build the net MRR movement bridge — starting MRR + new MRR (subscriptions created in the window) − churned MRR + reactivated MRR (subs that came back from cancelled) = ending MRR — and check it lands on today's measured MRR. Net churn below zero means expansion outran losses; that's the headline a count alone can never show you.

  7. Decide policy for the awkward cases: paused, dunning, prepaid, and $0

    Needs a human

    Four real cases distort the number if you don't rule on them first. Paused subscriptions: a 'paused' status is neither active nor cancelled, so leave it out of both — but track the count, because a wave of pauses is churn wearing a disguise. Dunning: a failed charge (GET /charges?status=ERROR) isn't a cancel yet, so it stays in MRR, but its total is at-risk MRR worth surfacing on its own line, since that's revenue you can still save. Prepaid: already handled in step 3 by using the charge cadence — just confirm you didn't double-count it as monthly. Free or 100%-off subscriptions: a $0 line is real churn-wise but drags ARPU to the floor, so exclude it from the revenue math and note the count. Write these rules down; the digest just reports them.

  8. Build and post the Slack digest — idempotent, loud on errors and on a clean week

    Runs itself

    Assemble a Block Kit message to a channel like #subscriptions. Lead with MRR and its delta versus last period, then the net movement bridge (new · churned · reactivated), then a compact row — active subs · subscribers · ARPU · subscriber churn % · revenue churn % — then the top three cancellation reasons and the at-risk MRR sitting in failed charges, with a button to the Recharge analytics page for anyone who wants to dig. Make it reliable: store the last period you posted (n8n static data or a cell in the Sheet) and skip if it already ran, so nothing double-posts; wrap the Recharge calls so an API error posts 'couldn't pull subscription data' instead of failing silently; and post the digest even on a week with zero cancellations, because the entire point is to stop checking by hand — an empty channel must never be allowed to mean 'no churn.'

02The stack

What it’s built on

Recharge
The source of truth for subscriptions. The Subscriptions API serves your active base and the period's cancellations (with reasons), the Charges API surfaces failed payments as at-risk MRR, and Recharge's own analytics is the figure your digest reconciles against so the team trusts it. Recharge's built-in dashboard shows some of this, but it lives behind a login, doesn't push, and reports in its definitions rather than yours.The Recharge API, webhooks, and analytics are included with your plan — this reads subscription data you already pay Recharge to hold, at no extra cost. (If you want real-time churn alerts on top of the digest, the subscription/cancelled and charge/failed webhooks are free to subscribe to.)
Google Sheets
The append-only historical ledger — and the reason churn rates are possible at all, since Recharge can't tell you what MRR was last month. One row per period (MRR, active subs, subscribers, ARPU) gives every rate its baseline and gives you a record you own, readable without any other tool.Free with any Google account; the Sheets API is free at this volume (one append per period). No upgrade needed to hold the ledger.
Slack
Where the digest lands so the founder and finance see MRR and churn without logging into Recharge. A free incoming webhook covers the post; Block Kit gives you the skimmable card with the movement bridge and the at-risk line; chat.postMessage adds threading or an @-mention if a churn spike warrants one.Incoming Webhooks and the Slack API are free on every plan, including the free tier — there's no upcharge to receive this post.
n8n
The glue that does the work: a Schedule node fires it each period, paginated HTTP Request nodes pull the active subscriptions, cancellations, and failed charges, a Code/Set node runs the interval normalization and the movement-bridge math, a Sheets node appends the snapshot, and a Slack node posts the card — plus the idempotency guard and the error-and-clean-week handling.n8n is open-source and self-hostable for free, which covers this end to end; their cloud tier just saves you running the server. This runs once a period over a few hundred records, so it sits comfortably inside the smallest plan either way.

03Questions

Before you build

Doesn't Recharge already show MRR and churn?

It does, and the recipe reconciles against it on purpose. The difference is push versus pull, your definitions versus theirs, and history you own. Recharge's analytics lives behind a login and reports MRR and churn in its own way; this delivers the numbers to the channel your team already lives in, on your definition of the period and what counts as churn, and — crucially — backed by a Google Sheet snapshot, because Recharge can't reconstruct what your MRR was on the first of last month. It also surfaces at-risk MRR sitting in failed charges, which the analytics page doesn't put next to your churn number.

How do I turn different billing intervals into one MRR number?

Normalize every subscription to a monthly figure before you add anything up — a quarterly, a 90-day prepaid, and a weekly refill bill on different clocks, so summing their raw prices is meaningless. For a monthly-unit sub, divide price × quantity by the charge frequency; for weekly, multiply by 4.3452 ÷ frequency; for daily, by 30.4375 ÷ frequency. Use the charge cadence (charge_interval_frequency), not the delivery cadence, so a prepaid box that bills once for three months reads as a third of its charge per month. Sum those normalized lines and you have MRR that's actually comparable period to period.

Why do I need a Google Sheet — can't the API just tell me the churn rate?

The API can tell you what's true right now, but a churn rate is a fraction over time: cancellations this period ÷ the subscriber base at the start of it. Recharge has no endpoint that returns last month's base, so without a stored snapshot you can only ever report a count, not a rate. The append-only Sheet fixes that by recording MRR and the active-subscriber count once per period, so the next run reads a real denominator. As a bonus it's a history you own outright — exportable, readable, and not locked to any tool.

What about subscriptions that paused or had a failed payment — are those churn?

Neither is a cancellation, but both matter, so the digest handles them separately. A paused subscription is out of both active MRR and churn, but its count is tracked, because a surge of pauses is churn in slow motion. A failed charge stays in MRR — the subscriber hasn't left — but its value is shown as at-risk MRR on its own line, because that's revenue still in dunning that you can recover before it becomes a real cancellation. Treating either one as instant churn would overstate your losses; ignoring them would hide the leading indicators.

Will the numbers match my Recharge dashboard?

Yes, if you do two things: normalize every interval to monthly with the charge cadence, and fix a single definition of churn and of 'active' before you build. The usual cause of a mismatch isn't a bug — it's summing raw subscription prices across different billing intervals, or counting cancelled customers in one place and cancelled subscriptions in another. Pin the definitions in step one, reconcile your MRR once against a known day in Recharge's analytics, and it lines up from then on.

Can I build this myself, or should you build it?

Every endpoint, field, interval conversion, and edge case is written out above on purpose — if you've got a few hours and some API comfort, build it and own it outright. If you'd rather not own the cursor pagination, the interval normalization, the snapshot ledger, and the movement-bridge math, we'll build it on the Recharge, Google Sheets, and Slack accounts you already pay for, reconcile it against your dashboard, document every piece, and hand you the keys. You own the workflow — no monthly retainer, and nothing stops working if you walk away. It starts with a free 20-minute teardown of how you track retention today, not a contract.

Lifecycle & marketing

Save at-risk subscriptions before they cancel

By the time the 'subscription cancelled' email lands, the decision is already made — and the next charge is already gone. But Recharge emits the signals that come before a cancel: an upcoming charge nobody managed, a second skip in a row, a card that just declined. Here's how to wire Recharge and Klaviyo so those signals trigger a save while the customer is still subscribed — and intercept the cancel itself instead of only logging it. It's mostly a native Recharge + Klaviyo build; the glue layer is optional.

  • Recharge
  • Klaviyo

Intermediate4-6 hrs

Reporting & dashboards

Send a daily revenue digest to Slack

Every morning starts the same way: open Shopify, wait for the dashboard to load, read yesterday's number, close the tab. This does it for you. Once a day it pulls yesterday's orders from Shopify, totals net sales, order count, AOV, and units, compares it to the same weekday last week so the number actually means something, and posts a clean digest to a Slack channel before you've finished your coffee. Build it yourself with the steps below, or we build it on your own accounts, wire the math and the comparison, and hand you the keys so you own it with no monthly fee. Free teardown of what you check by hand every morning first.

  • Shopify
  • Slack

Intermediate2-4 hrs

Reporting & dashboards

Track refund rate and reasons in a dashboard

Your refunds line goes up and all you can see is the dollar total — not what percentage of orders it is, whether it's getting worse, or which products and reasons are driving it. This wires the answer up: a Google Apps Script pulls every refund and its return reason from the Shopify Admin API into a logged-and-normalized refunds tab, alongside an orders tab for the denominator, and a dashboard tab built on QUERY formulas shows refund rate over time, reasons ranked, and the products doing the returning — recalculating on its own as new rows land. Build it yourself with the steps below, or we build it on your own Shopify and Google accounts, reconcile it against your Shopify reports, and hand you the keys so you own it with no monthly fee. Free teardown of your current refund reporting first.

  • Shopify
  • Google Sheets

Intermediate3-5 hrs

Reporting & dashboards

Post a daily Stripe payouts and fees summary to Slack

Stripe deposits money to your bank on a rolling schedule, nets out refunds and disputes, skims its fee, and tells you almost none of it unless you log in and dig. This closes that gap. When a payout lands, it pulls the exact transactions that make up the deposit, totals the gross, the fees, the refunds, and the net, works out your effective fee rate, and posts a clean summary to Slack — so the number in your bank account finally has a story attached. Build it yourself with the steps below, or we build it on your own Stripe and Slack accounts, reconcile it to the cent against your dashboard, and hand you the keys so you own it with no monthly fee. Free teardown of what you reconcile by hand first.

  • Stripe
  • Slack

Intermediate3-5 hrs