Revenue9 min readVisitTrack Team

MRR by Marketing Channel: Stripe Revenue Analytics

Split Stripe MRR, churn and expansion by the channel that first brought each customer: the join, the script, the math and a worked example with real numbers.

To see MRR by marketing channel, tag every Stripe subscription with your own user id, record which channel first brought each user to your site, and join the two: sum each active subscription's monthly-normalized amount by the user's first-touch channel. Do the same for new, expansion, contraction and churned MRR each month, and you can see not just which channels acquire customers but which ones keep paying.

Stripe knows the money and nothing about marketing; your analytics knows the marketing and, at best, the first payment. This guide shows the join, a script that produces the report, the formulas, and a worked example where the channel that looks best on first payments turns out to be the worst on retained revenue.

Key takeaways

  • MRR by channel is the sum of each customer's monthly-normalized subscription amount, grouped by the channel of their first visit.
  • Stripe doesn't store traffic source, so you need a shared key, usually your own user id, on both the subscription and the analytics record.
  • First-payment attribution answers which channel acquires customers; MRR by channel answers which channel's customers stay and grow.
  • Churn and expansion often differ more between channels than acquisition does, which changes LTV:CAC rankings.
  • Normalize yearly plans to monthly, exclude trials, and decide once how to treat past-due subscriptions.

Why can't Stripe show MRR by marketing channel on its own?

A Stripe subscription has a customer, prices, a status and metadata. It has no idea that the customer read a blog post in March, clicked a newsletter link in April and signed up in May. Stripe's own reports can break revenue down by product, price or country, but not by referrer or campaign, because that data never reaches Stripe unless you put it there.

Revenue attribution tools close part of the gap by passing an analytics visitor id into checkout. That is how VisitTrack credits a Stripe Checkout payment to the referrer, campaign and landing page of the visitor's first touch; the setup is in how to track revenue by traffic source. Be precise about what that gives you: the Stripe integration records the payment made in Checkout, which for a subscription is the first charge. Renewals are billed later as invoices, with no page involved. That answers “which channel acquires paying customers,” but not “which channel's customers are still paying in month nine.” For that, you need the join below.

What do you need before you can split MRR by channel?

PieceWhere it livesHow to get it
Your user id on every subscriptionStripe subscription metadataSet subscription_data.metadata.user_id when creating the Checkout Session
Your user id on the analytics sideAnalytics identify recordCall identify(userId) at signup or login
First touch per userAnalyticsReferrer, UTMs and landing page of the first visit, kept per visitor
Monthly amount per subscriptionStripePrice × quantity, normalized to a month, after discounts
A channel mappingYour spreadsheet or scriptRules that turn referrer + UTMs into a channel name
// When creating the Checkout Session (server)
const session = await stripe.checkout.sessions.create({
  mode: "subscription",
  line_items: [{ price: priceId, quantity: 1 }],
  client_reference_id: visitorId,               // first-payment attribution
  subscription_data: { metadata: { user_id: user.id } }, // the join key for MRR
  success_url: "https://example.com/welcome",
  cancel_url: "https://example.com/pricing",
});

On the analytics side, identify the user with the same id. In VisitTrack, every identified person keeps the source that first brought them in, and the people export includes it: GET /api/v1/people?format=csv returns externalId, plan and the first-touch referrer, UTM source, medium, campaign and landing path for each person. The Stripe-specific checkout setup is in the Stripe revenue docs.

How do you calculate MRR by channel, step by step?

  1. 1.List all subscriptions with status active (and past_due, if you count it), expanding prices.
  2. 2.For each, compute monthly amount: unit amount × quantity, divided by 12 for yearly prices (or by the interval in months), minus any recurring discount.
  3. 3.Read metadata.user_id. Subscriptions without it go to an “Unattributed” bucket; fix that at the source rather than guessing.
  4. 4.Export people with their first touch from analytics and map each to a channel.
  5. 5.Join on user id and sum monthly amounts by channel. That is MRR by channel.
  6. 6.Repeat at the end of each month and diff the snapshots per customer to get new, expansion, contraction and churned MRR by channel.
import Stripe from "stripe";
import { parse } from "csv-parse/sync";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

// 1. Monthly-normalized MRR per user from Stripe
const mrrByUser = new Map<string, number>();
for await (const sub of stripe.subscriptions.list({ status: "active", limit: 100 })) {
  const userId = sub.metadata.user_id ?? "unattributed";
  let cents = 0;
  for (const item of sub.items.data) {
    const { unit_amount, recurring } = item.price;
    if (!unit_amount || !recurring) continue;
    const months = recurring.interval === "year" ? 12 * recurring.interval_count
      : recurring.interval === "month" ? recurring.interval_count
      : recurring.interval === "week" ? recurring.interval_count / 4.345
      : recurring.interval_count / 30.44; // daily
    cents += (unit_amount * (item.quantity ?? 1)) / months;
  }
  // Apply recurring discounts here if you use coupons.
  mrrByUser.set(userId, (mrrByUser.get(userId) ?? 0) + cents);
}

// 2. First touch per user from VisitTrack
const res = await fetch("https://visitrack.app/api/v1/people?format=csv", {
  headers: { Authorization: `Bearer ${process.env.VISITRACK_API_KEY}` },
});
const people = parse(await res.text(), { columns: true }) as Record<string, string>[];
const channelByUser = new Map(people.map((p) => [p.externalId, toChannel(p)]));

// 3. Sum by channel
const mrrByChannel: Record<string, number> = {};
for (const [userId, cents] of mrrByUser) {
  const channel = channelByUser.get(userId) ?? "Unattributed";
  mrrByChannel[channel] = (mrrByChannel[channel] ?? 0) + cents / 100;
}
console.table(mrrByChannel);

function toChannel(p: Record<string, string>): string {
  const src = (p.firstTouchUtmSource || p.firstTouchReferrer || "").toLowerCase();
  if (p.firstTouchUtmMedium === "newsletter") return "Newsletter sponsorships";
  if (p.firstTouchUtmMedium === "cpc") return "Paid search";
  if (/google|bing|duckduckgo/.test(src)) return "Organic search";
  if (/ycombinator|reddit/.test(src)) return "Communities";
  return src ? "Other referral" : "Direct / unknown";
}

Store each month's per-user snapshot (user id, channel, monthly amount). Comparing two snapshots gives the MRR movements: a user present only in the new one is new MRR, absent from the new one is churned MRR, and a changed amount is expansion or contraction. If you want to check the arithmetic on a single month by hand, the MRR calculator does the same normalization.

Can you do this without writing a script?

Yes, with two exports and a spreadsheet, which is a reasonable way to start before automating anything. Export active subscriptions from the Stripe dashboard (customer, price, interval, quantity and the user_id metadata column), and export people with their first touch from your analytics as CSV. In the spreadsheet, add a monthly-amount column (divide yearly prices by 12), add a channel column to the people sheet with a few lookup rules, join the two on user id with a lookup function, and build a pivot table of monthly amount by channel.

Save a copy of the joined sheet at the end of every month. Two saved months are enough to compute new, churned, expansion and contraction MRR per channel; six are enough to start looking at cohorts. When the monthly copy-paste becomes annoying, that is the moment to turn it into the script above and run it on a schedule.

The same join works for other payment providers. Paddle, Polar, Lemon Squeezy and Razorpay all let you attach your own data to a checkout or subscription (custom data, metadata or notes fields), so put your user id there and join on it exactly as with Stripe.

What does MRR by channel look like with real numbers?

A worked, illustrative example: a SaaS at $18,400 MRR with 460 paying customers, at the end of September.

First-touch channelCustomersMRRARPA
Organic search140$5,880$42
Newsletter sponsorships60$3,000$50
Communities (HN, Reddit)90$2,970$33
Paid search70$2,520$36
Direct / unknown100$4,030$40
Total460$18,400$40
Illustrative MRR split by the channel of each customer's first visit.

The snapshot is useful, but the movements are where the decisions are. Here is the same business's September, diffed against August:

ChannelNew MRRExpansionContractionChurnedNet new MRRMRR churn rate
Organic search+$630+$210−$40−$170+$6303.2%
Newsletter sponsorships+$450+$150$0−$100+$5004.0%
Communities+$330+$30−$30−$300+$3010.2%
Paid search+$540+$40−$20−$290+$27012.9%
Direct / unknown+$400+$120−$50−$150+$3204.0%
Total+$2,350+$550−$140−$1,010+$1,750—
MRR churn rate = churned MRR ÷ MRR at the start of the month for that channel. Illustrative.

On first payments alone, paid search looks like the second-best channel: $540 of new MRR this month, behind only organic search. On retained revenue it is the worst: 12.9% of its MRR churned in a month, so nearly all of its new revenue is spent replacing customers it already lost. Communities have the same problem at a smaller scale. Newsletter sponsorships, which brought in less new MRR than paid search, are adding almost all of it to the base.

How does MRR by channel change LTV:CAC?

Continuing the example, compare the two paid channels using each one's own churn rate rather than a company-wide average:

Paid searchNewsletter sponsorships
Monthly spend$1,800$1,200
New customers15 at $369 at $50
CAC$120$133
Monthly MRR churn12.9%4.0%
Expected lifetime (1 ÷ churn)7.8 months25 months
LTV (ARPA × lifetime)$279$1,250
LTV:CAC2.39.4
Illustrative. Using one blended churn rate for both would rank them almost the same.

With a company-wide churn rate, these channels look similar: paid search even has the lower CAC. Per-channel churn reverses the decision. The definitions behind these numbers are in the glossary under LTV:CAC ratio and net revenue retention; the CAC calculator handles the acquisition side if your spend is spread across tools and people.

Which MRR metrics should you track per channel?

MetricFormulaQuestion it answers
MRR by channelSum of active monthly amounts, grouped by first touchWhere does today's recurring revenue come from?
New MRR by channelMRR of customers new this monthWhich channel is acquiring now?
MRR churn rate by channelChurned MRR ÷ starting MRRWhose customers leave?
Expansion rate by channelExpansion MRR ÷ starting MRRWhose customers grow into bigger plans?
Net revenue retention by cohortMRR of a cohort now ÷ its starting MRRAfter 6 or 12 months, did a channel's cohort grow or shrink?
Payback by channelCAC ÷ (ARPA × gross margin)How fast is spend recovered?

What mistakes make MRR by channel misleading?

  • Using last touch. A customer who first came from search and later clicked a retargeting email would be credited to email. For MRR by channel, use the first touch you recorded at signup and never overwrite it.
  • Counting trials as MRR. A trialing subscription hasn't paid anything; include it only once it converts.
  • Mixing yearly plans in at full value. A $480-a-year plan is $40 of MRR, not $480.
  • Ignoring the Unattributed bucket. If it is more than about 10% of MRR, fix the metadata or identify step before trusting the split.
  • Reading tiny channels. A channel with 8 customers can show 0% or 25% churn in a month by chance. Use trailing three-month averages for small channels.
  • Comparing cohorts of different ages. A channel you started last quarter has only young customers, who churn at different rates than old ones. Compare cohorts at the same age.

For the earlier question of which channels bring paying customers in the first place, including how much data you need before cutting a channel, see which marketing channels bring paying customers.

How do I see MRR by marketing channel in Stripe?

Stripe doesn't store marketing channels, so you need to join it with your analytics. Put your user id in the subscription's metadata, record each user's first-touch channel in analytics, then sum each active subscription's monthly amount by that channel.

Should MRR by channel use first-touch or last-touch attribution?

First touch, recorded at signup and kept fixed. It reflects which channel created the customer, and it doesn't change every time the customer clicks a later email or ad, which would make month-over-month comparisons meaningless.

How do I handle annual plans in MRR?

Divide the annual amount by 12 and count that as monthly recurring revenue for every month of the term. Don't add the full annual payment to the month it was collected.

What is a good MRR churn rate by channel?

There is no universal number; it depends on price point and customer type. The useful comparison is between your own channels: a channel whose MRR churn is several times higher than the others is buying customers who don't stay.

Does revenue attribution track subscription renewals?

It depends on the integration. VisitTrack's Stripe integration records the payment made through Checkout, which for subscriptions is the first charge; renewals billed as invoices aren't attributed. MRR by channel needs the subscription join described here.

How many customers do I need before MRR by channel is meaningful?

Roughly 30 or more customers per channel for monthly churn rates to be stable. Below that, use trailing three-month numbers and focus on cohort retention over several months.

Keep reading

See which channels actually bring paying customers

VisitTrack is cookie-free analytics with revenue attribution built in. One script tag, no consent banner, live in two minutes. 14 days free, no card required.

14-day free trial · No card required · Cookie-free · Cancel anytime

Or explore the live demo first →