Stripe integration

Stripe analytics: MRR, churn and revenue by traffic source

BugOtter reads your Stripe account with a restricted, read-only key and shows what each of your apps earns: MRR and how it moved, revenue after refunds, trials, cancellations and failed payments. With the script on your site, it also shows which traffic sources brought the customers who pay.

  • Read-onlyrestricted key
  • 2 minto connect
  • Every 15 minsync

Three things to know

  • It can only read

    A restricted key with Read on six resources. BugOtter never charges, refunds or emails anyone.
  • MRR per app

    Revenue, MRR, trials, cancellations and failed payments follow each product to its app.
  • Revenue by source

    Payment Links are linked with no code. Your own checkout needs one line of metadata.

Connect Stripe in two minutes

You create a key that can only read, paste it once, and tell BugOtter which product belongs to which app.

  1. Create a restricted key

    In Developers → API keys, click Create restricted key. Name it “BugOtter”.
  2. Give it Read on six resources

    Products, Prices, Invoices, Checkout Sessions, Refunds and Subscriptions. Nothing gets Write.
  3. Paste it on Revenue

    BugOtter checks it can list products, then stores it encrypted.
    Restricted keyrk_live_…
  4. Match products to apps

    BugOtter guesses from each product's URL and name. You correct the rest once.
  • Every permission starts at None. The key starts with rk_live_ (or rk_test_ in a sandbox).
  • BugOtter only ever shows the key's first 8 and last 4 characters.
  • Revenue, MRR and failed payments then follow the product to its app. A connected agent can match products for you with link_product, if you gave it write access.

90 days

The first sync reads 90 days of payments and every current subscription.

Then it repeats every 15 minutes, or when you press Sync now.

Steps checked Nov 4, 2026 against Stripe's restricted API keys guide. A secret key (sk_) also works, but there's no reason to hand over more than reading.

What you'll see

One app's revenue in the live demo: MRR, what moved it, and what's leaving soon.

One app's Revenue tab in BugOtter: MRR of $1,389, a waterfall of how MRR moved in 30 days, a forecast, revenue, paying subscribers and trials
  1. MRR per app and for all apps together.
  2. How MRR moved: new, upgrades, downgrades and cancellations.
  3. A 30-day forecast if the last 30 days repeat.
  4. Trials, and what they're worth if they pay.
  • Revenue after refunds for 7, 30 and 90 days or 12 months. Refunds land on the day Stripe made them, even when the original payment is older.
  • Leaving soon: subscriptions set to cancel, with the date and the reason the customer gave Stripe.
  • Failed payments. A card that keeps getting declined opens an issue on that app and alerts you, so you can check whether something in checkout changed.
  • Customers, anonymous: paying, cancelling or at risk (a failed card, or no visits for weeks), lifetime paid, and the source of their first visit. No names or emails are copied from Stripe.

Link each payment to the visit that brought it

This is what turns “Reddit sent 900 visitors” into “Reddit sent $240 of MRR”.

BugOtter traffic cards: channels with visitors, revenue and error rate, top pages, and countries with revenue
  1. Revenue per channel, next to its visitors.
  2. Errors per channel, so a source that breaks checkout stands out.
  3. Revenue per country, from the same visits.

Payment Links: nothing to do

If you sell with Stripe Payment Links, the BugOtter script finds every buy.stripe.com link on your pages and adds the visit id as client_reference_id.

Stripe keeps it on the Checkout Session. BugOtter matches the payment, and the subscription it starts, to the visit's source.

Your own Checkout Session: one line of metadata

First, read the visit id in the browser and send it to your server with the checkout request:

Browser
// In the browser, before you start the checkout
const visitorId = window.bo?.visitorId?.(); // "bo_…" or undefined

await fetch("/api/checkout", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ productId, visitorId }),
});

Then pass it along when you create the session. BugOtter reads bugotter_visitor_id from the session's metadata, and carries it to the subscription and every renewal invoice after it.

Server: create the Checkout Session
// On your server (stripe-node)
const session = await stripe.checkout.sessions.create({
  mode: "subscription", // or "payment"
  line_items: [{ price: priceId, quantity: 1 }],
  success_url: "https://yourapp.com/thanks",
  // Only when the browser sent one
  ...(typeof visitorId === "string" && visitorId
    ? { metadata: { bugotter_visitor_id: visitorId } }
    : {}),
});

Creating subscriptions with the API instead of Checkout? Put the same metadata on stripe.subscriptions.create. BugOtter reads it from the subscription and its invoices.

Not covered yet:

  • Payments made with a bare PaymentIntent and no invoice (a custom Elements flow).
  • Payment Links behind a Buy Button embed or your own domain, because the script only sees plain buy.stripe.com links.
  • By default the visit id lasts for the browser tab, so a visitor who comes back days later in a new tab counts as a new visit.

Prefer that your agent does it? The setup prompt on each app's Setup tab includes this step, written for your stack.

How MRR is counted for Stripe

Yearly ÷ 12

Each subscription item is its price times quantity, turned into a month.

Every 3 months ÷ 3, weekly × 4.35. A subscription with several items adds them up.
  • What counts: active and past-due subscriptions. Trials count once they pay. A subscription set to cancel counts until the day it ends.
  • Discounts: coupons aren't subtracted from MRR yet. That's the most common reason BugOtter's number is a little higher than Stripe's.
  • Tax: not included, unless your prices include tax.
  • Revenue is paid invoice lines plus completed one-time Checkout payments, minus refunds.
  • Currencies are never converted or added together. Sell in USD and EUR and you see both.

Want to check the math on your own numbers? The MRR calculator walks through annual plans, trials and churn.

Test mode first, then read-only for real

Test mode

Paste a rk_test_ key from a sandbox and BugOtter marks the connection Test mode, so you can try the whole flow with test cards before touching real data.

BugOtter holds one Stripe key at a time. When you're ready, disconnect (which removes the test payments it copied) and connect a live key.

Read-only, on purpose

BugOtter never charges, refunds, cancels or emails anyone. The key you made can't do any of that, and BugOtter's Stripe code only sends read requests.

Connecting or disconnecting Stripe is only possible in the app, never through the MCP server, so a coding agent can read your revenue but can't touch the connection. Disconnecting deletes the payments, subscriptions and products BugOtter copied from Stripe.

Stripe next to the other payment providers

BugOtter reads four providers. Here's what each one gives you.

What BugOtter reads from each payment provider
FeatureStripePolarLemon SqueezyCreem
Access
The key you give BugOtterYesRestricted key, Read onlyYesOrganization token, read scopesPartlyAPI key (can't be limited)PartlyAPI key (can't be limited)
Revenue
Revenue after refundsYesYesYesYes
MRR and how it movedYesYesYesYes
Trials and when they endYesYesYesYes
Cancellations scheduled (leaving soon)YesYesYesYes
Why customers cancelledYesYesNoNo
Refunds on the day they happenedYesPartlyWith refunds:readPartlyOrders from the last 90 daysPartlyDated by the payment
Failed payment alertsYesNoYesYes
Linking and testing
Revenue by traffic sourceYesPayment Links: automatic. Your checkout: metadataYesCheckout metadataNoNot yetPartlySubscriptions, via checkout metadata
Test modeYesrk_test_ keys, marked Test modeNoNot yet (the sandbox is a separate API)YesTest-mode API keysYescreem_test_ keys, marked Test mode

Questions

Can BugOtter charge, refund or email my Stripe customers?

No. The key you create only has Read permission, and BugOtter's Stripe code only makes read requests. It never creates charges, refunds, coupons or emails. Even a connected coding agent can't connect, disconnect or change Stripe through BugOtter.

Which Stripe permissions does the key need?

Read on Products, Prices, Invoices, Checkout Sessions, Refunds and Subscriptions. Leave everything else at None. If one is missing, BugOtter still connects and tells you which part it couldn't read.

Why is my MRR different from Stripe's?

BugOtter counts each active or past-due subscription at its list price times quantity, turned into a month. Stripe's own MRR subtracts coupons and may leave out past-due subscriptions. Trials don't count in either until they pay. The Revenue page has a "Why doesn't this match Stripe?" panel with every rule.

Do Stripe Payment Links get linked to traffic sources?

Yes, with no code. The BugOtter script adds the visit id as client_reference_id to every buy.stripe.com link on your pages, and the payment is linked to the visit's source when it syncs.

How far back does it go?

The first sync reads the last 90 days of payments and all current subscriptions. From then on BugOtter keeps the history itself, up to your plan's history length.

Can I connect more than one Stripe account?

One Stripe account per BugOtter account today. Products from that account are mapped to your apps, so one Stripe account selling several products covers several apps.

See what every app earns, and where it came from.

14 days of everything, no card needed. Plans from $10 a month, billed yearly.