How to Build a Monthly Subscription Churn Report from Stripe to Slack

Build a Spojit workflow that runs on the first of every month, pulls your Stripe subscriptions and charges, computes churn and MRR-change KPIs with the math connector, has an Agent-mode Connector node write a plain-language narrative of why customers left, and posts the finished report to a Slack channel.

What This Integration Does

Subscription churn is the single number every subscription business watches, but pulling it together by hand each month is tedious: someone has to export Stripe data, count which subscriptions cancelled, work out the revenue that walked out the door, and then explain the story to the team. This workflow does all of that automatically. On a fixed schedule it reads your live Stripe data, calculates the headline metrics (cancelled count, churn rate, lost monthly recurring revenue, and net MRR change), asks an Agent-mode Connector node to turn those numbers into a short readable narrative, and drops the result into the Slack channel where your team already talks about growth.

The run model is a Schedule trigger fired by a cron expression (for example the first of each month at 9am in your timezone). Each run reads the current state of your Stripe account through Connector nodes in Direct mode, reshapes and counts the data with Transform and math steps, generates the narrative with a Connector node in Agent mode, and finishes by calling Slack send-message. Nothing is written back to Stripe, so the workflow is read-only and safe to re-run: running it twice produces two report messages but never changes your billing data. Because it reads the live account at run time, the report reflects whatever cancellations and charges exist the moment it fires.

Prerequisites

  • A Stripe connection in Spojit (API key connection). See the Stripe connector article for setup.
  • A Slack connection authorized to post in your target channel, plus the channel ID (for example C0123ABCD) or name. See the Slack connector article.
  • The math and date connectors are built in and need no connection.
  • Decide the schedule and timezone up front, for example 0 9 1 * * in Australia/Sydney (09:00 on the 1st of every month).
  • Familiarity with handlebars variables such as {{ step.field }}. See working with variables and templates.

Step 1: Start with a Schedule trigger

Create a new workflow and set the Trigger node type to Schedule. A Schedule trigger uses a 5-field Unix cron expression plus an IANA timezone. Enter the cron and timezone for the cadence you want:

Cron:     0 9 1 * *
Timezone: Australia/Sydney

This fires at 09:00 on the first day of every month. The trigger output is { scheduledAt }, an ISO timestamp of the fire time, which you can reference downstream as {{ trigger.scheduledAt }}. If you want a second cadence (say a mid-month preview), a single Schedule trigger can hold multiple schedules. For step-by-step setup see setting up a Schedule trigger.

Step 2: Compute the reporting window with the date connector

Add a Connector node on the date connector in Direct mode to derive the start and end of last month, which you will use to filter charges. Use subtract and start-of / end-of against {{ trigger.scheduledAt }}.

First, a Connector node with the date tool start-of to get the first instant of the current month, then subtract one month to land in the previous month. A clean pattern is one node using start-of with unit month on the scheduled date, and a second node using subtract:

Tool:  subtract
date:  {{ month_start.result }}
amount: 1
unit:  month

Capture the window edges as {{ period_start.result }} and {{ period_end.result }}. You will also want a Unix epoch value for Stripe filters; the date tool unix converts an ISO date to a seconds timestamp, which is the format Stripe charge filters expect.

Step 3: Pull subscriptions and charges from Stripe (Direct mode)

Add a Connector node on the stripe connector in Direct mode using the list-subscriptions tool. Direct mode is deterministic, costs no AI credits, and is the right choice for predictable single-tool reads.

Run it once for currently active subscriptions and once for cancelled ones by setting the status field. Set limit to 100 (the maximum) and page with starting_after if you have more than 100:

Tool:   list-subscriptions
status: active
limit:  100
Tool:   list-subscriptions
status: canceled
limit:  100

Each call returns a list where every item exposes fields such as id, status, and customer. Reference the results as {{ active_subs.result.data }} and {{ canceled_subs.result.data }}. Add a third Connector node on stripe using list-charges to see the revenue that actually settled in the window; each charge exposes amount, currency, status, and customer.

Step 4: Shape the raw lists with a Transform node

Add a Transform node to pull just the fields you need before doing math. Subscription and charge payloads are large, and the metrics only need a handful of values: cancelled subscriptions, their plan amounts, and the count of active subscriptions. Use the Transform node to build a tidy object such as:

{
  "activeCount": {{ active_subs.result.data.length }},
  "canceledCount": {{ canceled_subs.result.data.length }},
  "lostMrrCents": ,
  "settledChargeCents": 
}

For summing a list of amounts, you can pull the numeric values with the array connector tool pluck (to extract the amount field into a flat list) and feed that list into the next step. Keep amounts in their smallest currency unit (Stripe reports cents) and convert to dollars at the very end to avoid rounding drift. For more on shaping data with the Transform node see using Transform nodes.

Step 5: Calculate churn and MRR KPIs with the math connector

Add Connector nodes on the math connector in Direct mode to compute the headline numbers. Use sum to total the cancelled plan amounts into lost MRR, percentage for the churn rate, and currency to format dollar values for the report.

Churn rate is cancelled subscriptions over the base of active-plus-cancelled. Use the percentage tool:

Tool:  percentage
value: {{ shaped.canceledCount }}
total: {{ shaped.activeCount }} + {{ shaped.canceledCount }}

Total the lost recurring revenue with sum over the plucked cancelled amounts, then convert cents to a clean dollar string with the currency tool. For net MRR change, use calculate with an expression that subtracts lost MRR from new or expansion MRR if you track it. Capture each result, for example {{ churn_rate.result }}, {{ lost_mrr.result }}, and {{ net_change.result }}. More math options are in the Math Tools article.

Step 6: Write the narrative with a Connector node in Agent mode

Add a Connector node in Agent mode to turn the numbers into a short, plain-language story of why customers left and how the month went. Agent mode lets the agent reason over the inputs you give it, and a Response Schema forces clean structured output you can drop straight into Slack.

Pass the computed KPIs and the cancelled-customer list into the prompt and ask for a tight summary. A good prompt is specific and references real variables:

Write a 4 to 6 sentence monthly churn summary for the team.
Active subscriptions: {{ shaped.activeCount }}
Cancelled this period: {{ shaped.canceledCount }}
Churn rate: {{ churn_rate.result }}%
Lost MRR: {{ lost_mrr_formatted.result }}
Net MRR change: {{ net_change.result }}
Cancelled customers and plans: {{ canceled_subs.result.data }}
Explain in plain language what likely drove the cancellations
(plan size, recency, clustering) without inventing data.

Set a Response Schema so the node returns a predictable object, for example a single narrative string plus an optional headline. Reference the output downstream as {{ churn_story.data.narrative }}. You can scaffold this whole step by asking Miraxa, the intelligent layer across your automation, to "add a Connector node in Agent mode that summarizes my churn KPIs," then fine-tune the prompt in the properties panel. For when to use each mode see choosing between Agent and Direct mode.

Step 7: Post the report to Slack

Finish with a Connector node on the slack connector in Direct mode using the send-message tool. Map the channel and assemble the message body from your KPIs and the generated narrative:

Tool:    send-message
channel: C0123ABCD
text:    *Monthly Churn Report - {{ trigger.scheduledAt }}*
         Active: {{ shaped.activeCount }} | Cancelled: {{ shaped.canceledCount }}
         Churn rate: {{ churn_rate.result }}%
         Lost MRR: {{ lost_mrr_formatted.result }} | Net MRR change: {{ net_change.result }}

         {{ churn_story.data.narrative }}

If you would rather find the channel by name, add a Connector node on slack with list-channels first and pass the matched channel ID into send-message. Save the workflow and enable it so the schedule starts firing.

Tips

  • Keep all money in cents through the math steps and only format with the currency tool at the end. Mixing dollars and cents mid-pipeline is the most common cause of wrong totals.
  • Stripe list calls return at most 100 records. If you have more active subscriptions than that, page with starting_after using the last id from the previous page, or narrow with the customer filter.
  • Agent mode costs AI credits while Direct mode does not. Keep every deterministic read (subscriptions, charges, math, Slack) in Direct mode and reserve Agent mode for the single narrative step.
  • Add a Slack thread follow-up by capturing the message timestamp from send-message and posting KPI detail as replies, keeping the channel tidy.

Common Pitfalls

  • Timezone drift: the cron fires in the timezone you set, not UTC. A report meant for "the 1st" in Australia/Sydney will land on a different calendar day if you leave the timezone blank or set UTC.
  • Counting the wrong base: churn rate must divide cancelled subscriptions by the start-of-period base, not by the current active count alone. Decide your denominator and keep it consistent month to month.
  • Status filters: a subscription set to cancel at period end may still report active until the period closes. If your numbers look low, also inspect past_due and unpaid statuses, which often precede churn.
  • Slack channel access: if send-message fails, confirm the Slack connection is a member of the target channel. Posting to a private channel the connection has not joined returns an error.

Testing

Before enabling the schedule, validate the logic on a small scope. Temporarily lower the Stripe limit to a handful of records, or filter list-subscriptions by a single test customer, and run the workflow with the Run button (the Manual run executes every step except the schedule wait). Point the Slack send-message node at a private test channel first, confirm the KPIs in the message match what you see in the Stripe dashboard, and read the execution log to verify each Transform and math step produced the value you expected. Once the numbers reconcile, restore the full limits and target channel, then enable the workflow so the Schedule trigger takes over. See understanding execution logs to trace any step that misbehaves.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.