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 ofmetadata.
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.
Create a restricted key
In Developers → API keys, click Create restricted key. Name it “BugOtter”.Give it Read on six resources
Products, Prices, Invoices, Checkout Sessions, Refunds and Subscriptions. Nothing gets Write.Paste it on Revenue
BugOtter checks it can list products, then stores it encrypted.Restricted keyrk_live_…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_(orrk_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.
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.

- MRR per app and for all apps together.
- How MRR moved: new, upgrades, downgrades and cancellations.
- A 30-day forecast if the last 30 days repeat.
- 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”.

- Revenue per channel, next to its visitors.
- Errors per channel, so a source that breaks checkout stands out.
- 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:
// 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.
// 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.comlinks. - 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.
- 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.
| Feature | Stripe | Polar | Lemon Squeezy | Creem |
|---|---|---|---|---|
| Access | ||||
| The key you give BugOtter | YesRestricted key, Read only | YesOrganization token, read scopes | PartlyAPI key (can't be limited) | PartlyAPI key (can't be limited) |
| Revenue | ||||
| Revenue after refunds | Yes | Yes | Yes | Yes |
| MRR and how it moved | Yes | Yes | Yes | Yes |
| Trials and when they end | Yes | Yes | Yes | Yes |
| Cancellations scheduled (leaving soon) | Yes | Yes | Yes | Yes |
| Why customers cancelled | Yes | Yes | No | No |
| Refunds on the day they happened | Yes | PartlyWith refunds:read | PartlyOrders from the last 90 days | PartlyDated by the payment |
| Failed payment alerts | Yes | No | Yes | Yes |
| Linking and testing | ||||
| Revenue by traffic source | YesPayment Links: automatic. Your checkout: metadata | YesCheckout metadata | NoNot yet | PartlySubscriptions, via checkout metadata |
| Test mode | Yesrk_test_ keys, marked Test mode | NoNot yet (the sandbox is a separate API) | YesTest-mode API keys | Yescreem_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.