WorkflowsMCP

Runway & burn-rate digest: Stripe → Notion → Slack

Stripe MRR + Notion spend & cash → weekly runway numbers posted straight to Slack.

@workflowsmcpVerified finance-opsrunwayburn-ratefoundersmetrics

Runway needs MRR, monthly spend, and cash — Stripe holds only the first. This pulls MRR from Billing Analytics (any Billing account), reads one hand-typed spend-and-cash row per month from Notion, computes burn and runway, and posts to Slack: a founder number board, not an investor narrative you edit before sending. Notion formulas cannot call Stripe, so the cross-source math lives nowhere else. Every Slack message labels cash as hand-entered, not bank-reconciled; at-or-below-MRR spend reports cash-flow positive instead of a nonsense runway.

How it flows

  1. 01

    Monday 08:00 trigger

    Weekly kickoff early enough for the founder board before the week starts. Wire it to whatever scheduler drives this workflow.

  2. 02

    Stripe MRR pulled

    Billing Analytics' "Subscription metrics summary" leads for the current MRR absolute. After Configure definition changes, Stripe says those can take 24–48 hours to apply — that is not a documented lag of the underlying MRR figures; treat the CSV as period-end analytics, not intra-day live balance. The paginated subscription-list fallback covers accounts without Analytics available.

  3. 03

    Notion spend and cash balance read

    One query against this month's single Monthly Opex row. Amount and Cash on hand are already hand-typed summaries — no cross-row summing for the job to get wrong.

  4. 04

    Burn computed — zero and negative branches first

    Burn = this month's spend − MRR. When spend is at or below revenue, burn is zero or negative: the job reports "cash flow positive — no runway to compute" and skips the division. Never post a negative or infinite runway month-count.

  5. 05

    Runway computed from hand-typed cash

    Only when burn is positive: runway months = Cash on hand ÷ burn. Cash is the Notion hand-entered figure from the prior step — not a bank feed, not Stripe Balance.

  6. 06

    Digest posted to Slack

    MRR, monthly burn, cash on hand, and runway in one Block Kit message — same template every week, including the permanent hand-entered-cash context footer. When the prior step reported cash-flow positive, put that phrase in the Runway field (do not invent a month-count) and still ship the footer; never drop the disclaimer on the profitable branch.

Set up each app

Work through these in order — later apps usually need a token or an id from an earlier one.

Stripe

Current MRR — revenue side only

  1. 01

    Create a restricted, read-only key

    Developers → API keys → Create restricted key. Grant Read on Subscriptions and Invoices; leave everything else off, especially Balance, Payouts, Refunds, and any Write permission. A weekly read of a few endpoints sits far under Stripe's live rate limits (100 req/s global), so throttling is not a concern for this job.

  2. 02

    Confirm Billing Analytics and export the MRR summary

    Dashboard → Billing → Analytics → Configure. Note how MRR and "active" are computed (discounts, trials) and keep that as your baseline. Any Stripe Billing account already includes this panel and its three CSV exports — it is not a higher-tier add-on. Prefer the "Subscription metrics summary" CSV for the current MRR absolute. After you change Configure definitions (discounts / active-subscriber rules), Stripe says those configuration changes can take 24–48 hours to apply — that is not a documented 24–48h lag of the underlying MRR figures themselves; treat the CSV as period-end analytics, not intra-day live balance.

  3. 03

    Fallback: sum MRR from the subscription list

    When Billing Analytics is unavailable, list active subscriptions and normalise yourself: yearly price ÷ 12, monthly as-is, all in minor units. Paginate to the end — the default page is 10, and a truncated page silently looks like the company shrank. Auth is `Authorization: Basic` with the restricted key as the username and an empty password (`-u "$STRIPE_RESTRICTED_KEY:"`).

    Active subscriptions, paginated
    curl -G https://api.stripe.com/v1/subscriptions \
      -u "$STRIPE_RESTRICTED_KEY:" \
      -d status=active \
      -d limit=100
    # yearly price / 12, monthly as-is, all in minor units; page to the end
  4. 04

    Know what Stripe cannot tell you

    GET /v1/balance returns Stripe's own processing float — money collected but not yet paid out to your bank — not your company bank balance, and never payroll or rent. This workflow does not call it. Cash on hand comes from Notion, hand-typed, in the next section; keeping Stripe out of that number is what keeps this a revenue-side-only integration.

Notion

Monthly spend total and hand-typed cash balance

  1. 01

    Create a "Monthly Opex" database — one row per month

    One row per month, never one row per receipt. Properties: Month (Date), Category (Select: Payroll / Opex / One-time — label the dominant bucket, or leave blank), Amount (Number — that month's total spend, typed once as a summary), Cash on hand (Number — your bank balance as of today, typed once and updated when you re-check). Monthly totals, not line items, keep the weekly job from summing half-entered rows and understating burn.

  2. 02

    Share the database with your integration

    Create an Internal Integration Token, then open the database → ••• (top right) → Connections → Add connection → your integration. A database you can plainly see still 404s until this step runs. Default "Read content" is enough — do not grant Insert or Update; this job never writes back to Notion.

  3. 03

    Retrieve the data source id once, then query this month

    GET /v1/databases/{database_id} and keep the data_source_id it returns. The database query endpoint is deprecated as of API version 2025-09-03 — every read goes through POST /v1/data_sources/{data_source_id}/query with Notion-Version: 2026-03-11. Filter Month on_or_after the first of this month; expect one row, and read Amount and Cash on hand from it.

    POST /v1/data_sources/{data_source_id}/query · Notion-Version: 2026-03-11
    {
      "filter": {
        "property": "Month",
        "date": { "on_or_after": "2026-08-01" }
      }
    }

Slack

Where the number lands — no editing in between

  1. 01

    Create the app with two scopes

    api.slack.com/apps → Create New App → From scratch → OAuth & Permissions → Bot Token Scopes. Add chat:write (post the digest) and channels:read (resolve the channel name to an id) — nothing else. This is a one-way broadcast: no channel history to read, no Slack user to map back to an external identity.

  2. 02

    Install to the workspace and invite the bot

    Install the app, then /invite it into #finance-ops (or another private finance channel). Stick to a channel id resolved with channels:read — a DM path needs extra scopes and an open-conversation step this workflow does not set up. Runway is a whole-company number that never routes to a specific person.

  3. 03

    Build the message template with the cash disclaimer baked in

    Use Block Kit: one section with four fields (MRR, monthly burn, cash on hand, runway) plus a permanent context footer. The cash-balance caveat is part of the template, not a setup note someone might trim later — every weekly post reuses this same payload, so the disclaimer ships with every message. Put a short fallback in text for the notification tray. On the cash-flow-positive branch, replace the Runway field value with "cash flow positive — no runway to compute" and keep the context footer unchanged.

    POST https://slack.com/api/chat.postMessage
    {
      "channel": "C0123FINOPS",
      "text": "Runway digest: 8.4 months left",
      "blocks": [
        { "type": "section", "fields": [
          { "type": "mrkdwn", "text": "*MRR:*\n$42,100" },
          { "type": "mrkdwn", "text": "*Monthly burn:*\n$18,300" },
          { "type": "mrkdwn", "text": "*Cash on hand (Notion, manual):*\n$154,000" },
          { "type": "mrkdwn", "text": "*Runway:*\n8.4 months" }
        ]},
        { "type": "context", "elements": [
          { "type": "mrkdwn", "text": "Cash balance is hand-entered in Notion — Stripe has no bank data. Treat this as an estimate, not a reconciled figure." }
        ]}
      ]
    }