Skip to content

5 min read

How to see which blog posts bring paying customers

Pageviews and signups don't tell you which posts make money. Here is how to link a first visit to a Stripe subscription, with code you can copy, and what to expect from the numbers.

Most founders who write content can tell you which posts get traffic. Far fewer can tell you which posts brought someone who is still paying three months later. The two lists are rarely the same, and the second one is the one that should decide what you write next.

This post walks through how to build that second list: what data you need, where it lives, how to join it, and what the numbers will and won't tell you.

Why analytics tools stop short

Google Analytics, Plausible and similar tools record visits and, if you set them up, conversions like "signed up". They don't see what happens after: the trial that converted in week two, the upgrade in month three, the churn in month five. That lives in Stripe or RevenueCat.

Billing tools have the opposite problem. Stripe knows every subscription, every upgrade and every refund, but it has no idea that the customer first arrived on /blog/stripe-vs-paddle from a Google search.

To know which posts make money, you need to carry one piece of information across that gap: where this person first landed, attached to the customer record that pays.

The three things to capture

1. The first touch. On a visitor's first page view, record the landing page, the referrer and any UTM parameters. Store it once and don't overwrite it on later visits.

2. The identity link at signup. When the visitor creates an account, save that first touch on your user record, or send it somewhere along with your user ID.

3. The user ID on the subscription. When they pay, make sure the Stripe customer or subscription carries the same user ID. With Stripe Checkout, set it in metadata:

const session = await stripe.checkout.sessions.create({
  mode: "subscription",
  line_items: [{ price: priceId, quantity: 1 }],
  customer_email: user.email,
  subscription_data: { metadata: { user_id: user.id } },
  success_url: `${origin}/welcome`,
  cancel_url: `${origin}/pricing`,
});

With those three in place, every subscription can be traced back to a page.

A minimal do-it-yourself version

If you want to try this before buying anything, here is the smallest version that works.

Capture the first touch in the browser:

// Runs on every page; only the first visit is kept.
if (!localStorage.getItem("first_touch")) {
  localStorage.setItem(
    "first_touch",
    JSON.stringify({
      page: location.pathname,
      referrer: document.referrer || null,
      utm: Object.fromEntries(new URLSearchParams(location.search)),
      at: new Date().toISOString(),
    }),
  );
}

Send it with your signup request and store it on the user row. Then, once a month, join users to subscriptions and group by landing page:

select
  u.first_touch->>'page' as landing_page,
  count(distinct s.customer_id) as paying_customers,
  sum(s.monthly_amount) as mrr
from users u
join subscriptions s on s.user_id = u.id
where s.status = 'active'
group by 1
order by mrr desc;

This will get you a rough answer. It will also run into every problem in the next section.

Where it gets hard

Consent. In the EU, writing to localStorage for analytics needs consent. Visitors who refuse leave no first touch. A careful setup keeps the landing page in memory only and passes it along if the visitor signs up in the same session, which covers people who sign up on the page they landed on.

Ad blockers. Scripts loaded from third-party analytics domains get blocked often. A first-party script served from your own domain is blocked much less.

Cross-device journeys. Someone reads your post on their phone and signs up a week later on their laptop. The laptop has no first touch. There's no honest fix for this short of a login on both devices, so measure it rather than hide it.

MRR that moves. If a customer starts on a €9 plan and upgrades to €29, which number belongs to the post? Pick a rule and keep it. Crediting the MRR at first payment keeps past months stable; showing current MRR on each customer tells you who grew.

Annual plans, trials and currencies. A €290 yearly plan is €24.17 of MRR, a trial is zero, and a customer paying in dollars needs converting at a consistent rate. Your query above needs all of that.

Report the gap, not just the winners

The most useful number in attribution isn't the top post. It's the attributed share: how much of your new MRR you could link to any source at all.

If 70% of new MRR is attributed and one post accounts for a third of it, that's a strong signal. If 20% is attributed, the ranking is mostly noise, and the first job is fixing capture: is identify() firing, does Stripe carry the user ID, is the script blocked?

Always look at the two numbers together.

What about keywords?

Once you know a post's revenue, it's tempting to ask which search query brought it. Google doesn't pass the query to your site, so you can't know it for any one visitor. What you can do is split a page's revenue by each query's share of that page's clicks in Search Console. That's a model, not a measurement, and it should be labeled as one. We cover the details, including the queries Google hides, in Search Console hidden queries.

Expect a warm-up

Attribution only works for customers who arrive after you start capturing first touches. Customers who signed up before have no landing page and stay unattributed. Plan on about 30 days of new signups before the ranking means much, longer if you add only a handful of customers a month.

Or let Joinsight do it

Joinsight is this whole setup as a product: a first-party snippet under 2 KB with a consent API, identify() at signup, read-only connections to Stripe, RevenueCat and Search Console, and a Content screen that ranks pages, channels and posts by the MRR they brought, each figure labeled measured or modeled, with the attributed share on top. The same numbers arrive in a Monday email and through an MCP server you can ask from Claude.

See what makes you money.

Connect Stripe or RevenueCat and Search Console, and you see combined MRR and 16 months of search history on day one. Page and keyword revenue follow as the snippet collects first touches.

No card required. The trial clock starts when your first revenue source connects.