---
name: stripe-monthly-churn-report
description: "On the 1st: last month's customer churn and revenue churn from Stripe, split by the reason customers gave when they canceled. Use when the user asks for this, or when it runs on a schedule."
---

# Stripe monthly churn report

On the 1st: last month's customer churn and revenue churn from Stripe, split by the reason customers gave when they canceled.

Churn is the number everyone quotes and few compute the same way twice. This skill fixes the definition: paying customers and MRR on the first day of the month, against what was canceled by the last day, with failed payments kept apart from people who chose to leave.

The half that changes decisions is the second one. Stripe stores the reason and comment a customer gives in the customer portal on the subscription itself, and the agent groups them, so you can tell a pricing month from an onboarding month.

## Two ways to get the data

Use the API or MCP route when you have an MCP server, a CLI or an API token for the app. Otherwise, if you can drive a browser the user is signed in to (Claude in Chrome, a Playwright or Chrome DevTools MCP server), use the browser route. Say which one you used.

## Route A: API or MCP server

### What you need

- A restricted Stripe key that reads Subscriptions, Customers and Prices, in `STRIPE_API_KEY`, or the Stripe MCP server.
- Cancellation reasons turned on in the Stripe customer portal settings, so there is feedback to group.

If something is missing, say what and stop; do not invent numbers.

### Steps

1. **Find last month's cancellations** (Stripe): Page through `GET /v1/subscriptions?status=canceled` and keep subscriptions whose `ended_at` falls inside last month. Drop trials that ended without a single paid invoice: they never paid, so they are not churn.
2. **Rebuild the starting base** (Stripe): List subscriptions with `status=all` and keep the ones that were paying on the 1st: `start_date` before it, out of trial by then, and not ended before it. Count distinct customers and their MRR, normalized to a month after discounts.
3. **Read the reasons** (Stripe): From each canceled subscription take `cancellation_details.reason` (`cancellation_requested`, `payment_failed`, `payment_disputed`), `cancellation_details.feedback` (`too_expensive`, `missing_features`, `switched_service`, `unused`, `too_complex`, `low_quality`, `customer_service`, `other`) and the free-text `comment`.
4. **Compute both rates**: Customer churn is customers with no active subscription left at month end divided by paying customers on the 1st. Revenue churn is the MRR of canceled subscriptions divided by MRR on the 1st. Keep voluntary and `payment_failed` apart.
5. **Write the report**: Both rates next to the month before, churned MRR, the reasons ranked by MRR lost, and up to three customer comments that say something specific.

## Route B: the browser

### What you need

- Signed in to the Stripe Dashboard with the Analyst role or higher, live mode on, and cancellation reasons turned on in the customer portal settings so there is feedback to read.

If something is missing, say what and stop; do not invent numbers.

### Steps

Start at https://dashboard.stripe.com/billing. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.

1. **Read churned MRR for last month**: Open `https://dashboard.stripe.com/billing` in live mode and go to the **Churn** page. Set the date range to last month. Click **Explore** on **Churned revenue**: the table separates churned MRR from contraction MRR, and clicking the churn cell lists the customers who canceled. Take the churned MRR and the customer count, leaving contraction out.
2. **Read the base on the 1st**: On the **Revenue** page set the range to end on the 1st of last month and read **MRR** for that day; on the **Subscribers** page read **Active subscribers** for the same day. Those are the base for both rates.
3. **List the cancellations with their reasons**: Open `https://dashboard.stripe.com/subscriptions`, filter **Status** to **Canceled** and narrow to subscriptions that ended last month (**Export** the list and filter the CSV by its canceled-at column when the filter does not go by end date). Open each subscription: the page shows the cancellation reason, and the feedback and comment the customer left in the customer portal when there is one. Skip trials that never paid.
4. **Compute both rates**: Customer churn is churned customers divided by active subscribers on the 1st; revenue churn is churned MRR divided by MRR on the 1st. Keep cancellations caused by failed payments apart from voluntary ones, and group the rest by feedback, ranked by MRR lost.
5. **Compare and write**: Shift the Churn page range to the month before for the comparison, then write both rates, churned MRR, the reasons table and up to three specific comments.

Note: Stripe's headline Subscriber churn rate is a trailing 30-day figure that adds new subscribers to the base, and its Churned revenue includes downgrades, so read the Explore split rather than the headline. Feedback only exists for cancellations made in the customer portal or passed by your app; dashboard cancellations come through blank. Past a hundred cancellations a month, the subscription-by-subscription reading is slow and the export is the way.

## The report

- **Revenue churn** and **customer churn** for last month, with the month before.
- **Churned MRR** and the number of customers lost, voluntary and involuntary.
- **Reasons** ranked by MRR lost.
- Up to three comments worth reading, quoted as written.
- One line on the pattern, for example "Three of four too_expensive cancellations were Starter accounts in their second month."

## Post it to Briefkin

When `BRIEFKIN_TOKEN` is set, or `~/.config/briefkin/env` exists (the installer from https://briefkin.dev/docs writes it), also send the result to the user's Briefkin board as one card. Keep the `item_key` the same on every run so the card updates in place, use a new `event_id` per run, and put today's real numbers in place of the example ones. If the request fails, say so once and carry on.

```bash
[ -n "${BRIEFKIN_TOKEN:-}" ] || . ~/.config/briefkin/env
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
  -H "authorization: Bearer $BRIEFKIN_TOKEN" \
  -H "content-type: application/json" \
  -d @- <<'JSON'
{
  "v": 1,
  "event_id": "stripe-monthly-churn-report/<date of this run, e.g. 2026-10-16>",
  "item_key": "stripe-monthly-churn-report",
  "project": "Acme",
  "source": "Stripe monthly churn report skill",
  "kind": "metric",
  "title": "September revenue churn: 3.1%",
  "metric": {
    "label": "Revenue churn",
    "value": 3.1,
    "unit": "%",
    "better": "lower",
    "period_start": "<start of the period, ISO 8601>",
    "period_end": "<end of the period, ISO 8601>"
  },
  "glance": [
    {
      "type": "Grid",
      "props": {
        "columns": 3
      },
      "children": [
        {
          "type": "Stat",
          "props": {
            "label": "Customer churn",
            "value": 2.4,
            "unit": "%",
            "better": "lower"
          }
        },
        {
          "type": "Stat",
          "props": {
            "label": "Churned MRR",
            "value": 1402,
            "prefix": "$",
            "better": "lower"
          }
        },
        {
          "type": "Stat",
          "props": {
            "label": "Customers lost",
            "value": 14,
            "better": "lower"
          }
        }
      ]
    }
  ],
  "widget": [
    {
      "type": "Table",
      "props": {
        "columns": [
          "Reason",
          "Customers",
          "MRR lost"
        ],
        "align": [
          "left",
          "right",
          "right"
        ],
        "rows": [
          [
            "switched_service",
            2,
            "$460"
          ],
          [
            "too_expensive",
            4,
            "$318"
          ],
          [
            "unused",
            3,
            "$244"
          ],
          [
            "missing_features",
            2,
            "$210"
          ],
          [
            "payment_failed",
            3,
            "$170"
          ]
        ]
      }
    },
    {
      "type": "Text",
      "props": {
        "text": "Base on Sep 1: 580 paying customers, $45,200 MRR.",
        "variant": "muted"
      }
    },
    {
      "type": "Callout",
      "props": {
        "text": "Both switched_service comments mention bulk export, and they were the two largest accounts lost.",
        "tone": "info"
      }
    }
  ],
  "size": "md"
}
JSON
```

Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.

Set `period_start` and `period_end` to the window the number covers: the tile names it from them ("Last 7 days", "Today", "This month"). For a count right now (open items, a balance), send `"period_label": ""` so no period shows; for another name, send it as `period_label` (at most 24 characters).

## Run it on a schedule

Suggested: 1st of the month at 9:00 (`0 9 1 * *`). From cron, with the agent signed in on that machine:

```bash
0 9 1 * * claude -p "Run the stripe-monthly-churn-report skill"
# Codex: codex exec, Gemini CLI: gemini -p, Copilot CLI: copilot -p, OpenCode: opencode run
```

Or use the agent's own scheduler (Claude Code routines, Codex automations, Claude or ChatGPT scheduled tasks): https://briefkin.dev/skills/stripe-monthly-churn-report#schedule

Source: https://briefkin.dev/skills/stripe-monthly-churn-report
