Blog
Revenue attribution12 min readVisitTrack Team

How to Track Revenue by Traffic Source

Pass an anonymous visitor id into Stripe Checkout, read it back from the webhook, and every payment lands under the referrer, campaign and page that earned it.

To track revenue by traffic source, you need one thread that connects a browser visit to a payment: give every visitor an anonymous id, pass that id into your checkout (Stripe calls the field client_reference_id), and when the payment webhook arrives, look the id back up and credit the payment to the referrer, UTM campaign and landing page that visitor came from. Everything else in this guide is detail on doing that reliably, without double counting, and without lying to yourself about what the numbers mean.

Key takeaways

  • Stripe knows who paid but not where they came from; your analytics knows where visitors came from but not who paid. Revenue attribution is the join between the two.
  • The join key is a single anonymous visitor id passed into Checkout as client_reference_id (or the equivalent metadata field in Polar, Paddle, Lemon Squeezy and Razorpay).
  • Record payments from a signed server webhook, never from a browser thank-you page, or ad blockers and refreshes will corrupt the numbers.
  • Pick an attribution model on purpose: first touch answers “what created this customer”, last touch answers “what closed them”, and they routinely disagree by 30% or more for SaaS.
  • Expect 10–30% of payments to be unattributable at first (ad blockers, cross-device purchases, payments outside Checkout) and report that share instead of hiding it.

Why can't Stripe tell you which traffic source made the money?

Stripe sits at the very end of the journey. By the time a customer types a card number, they are on checkout.stripe.com (or your embedded form), and the referrer, the UTM tags on their first visit and the blog post that convinced them are three or four page loads in the past. Stripe stores what it needs to process money — amount, currency, customer email, product — and nothing about acquisition. The Stripe dashboard will happily tell you that you made $4,180 last month. It will never tell you that $2,900 of it came from people who first found you through a single comparison page.

Your web analytics has the opposite problem. Tools like Google Analytics, Plausible or Fathom know that 1,240 people arrived from a Hacker News thread, but none of those rows carry a dollar amount, because the payment happened on a different system. That is why most founders end up guessing: they see a traffic spike and a revenue spike in the same week and assume one caused the other. Sometimes it did. Often the revenue came from a two-month-old newsletter mention that only now converted.

The fix is not a smarter dashboard, it is a shared key. If the analytics side and the payment side both know the same anonymous id, you can join them, and then every payment row inherits everything analytics knew about that visitor. That is what revenue attribution actually is, stripped of vendor language.

What do you need before you start?

PieceWhat it doesWhere it lives
An analytics script that keeps a visitor idRecords referrer, UTM source/medium/campaign and landing page per visitor, and keeps one stable anonymous id for themEvery page of your site
A checkout that accepts your own referenceCarries the visitor id through the payment so it comes back in the webhookStripe client_reference_id, Polar or Paddle metadata, Lemon Squeezy custom data
A signed payment webhookTells the attribution side a payment succeeded, server to servercheckout.session.completed in Stripe
A refund webhookSubtracts refunded money from the source that got creditcharge.refunded in Stripe
A place to read resultsRevenue grouped by source, campaign and landing pageYour analytics dashboard or a spreadsheet
The five parts of any revenue-by-source setup, regardless of tool.

If you already run a Stripe Checkout integration, the only code you will write is about ten lines: read an id in the browser, send it to the endpoint that creates the Checkout Session, and set one field. The rest is configuration.

How do you connect Stripe payments to traffic sources, step by step?

This walkthrough uses Stripe Checkout because it is what most SaaS products and indie projects use. The same seven steps apply to other providers; only the field name changes (see the table further down).

  1. 1.Install an analytics script that assigns each browser an anonymous visitor id and records the first referrer, UTM parameters and landing page for that id. With VisitTrack this is the single script tag from the install guide; the id is exposed as window.visitrack.visitorId().
  2. 2.Tag your inbound links so “where they came from” is more specific than a domain. A newsletter sponsorship should arrive as utm_source=newsletter-name&utm_medium=sponsorship&utm_campaign=2026-10-launch, not as a bare referrer. Build them with a UTM builder so the naming stays consistent.
  3. 3.On the page that starts checkout, read the visitor id in the browser and send it to your server along with the price id. Do this before redirecting — once the visitor is on Stripe's domain, your page's storage is out of reach.
  4. 4.On the server, create the Checkout Session with client_reference_id set to that id. Stripe accepts up to 200 characters there and returns it untouched in the session object.
  5. 5.In Stripe, add a webhook endpoint for checkout.session.completed and charge.refunded, and store the signing secret (whsec_…) on the attribution side so every delivery can be verified.
  6. 6.When checkout.session.completed arrives, verify the signature, read client_reference_id, look up the visitor, and record the payment with that visitor's source, campaign and landing page. Key it on the session or payment id so retried deliveries count once.
  7. 7.Run one test-mode purchase from a tagged link (for example ?utm_source=test-run) and confirm the payment shows up under that source. Then check the webhook delivery log: a 200 response that says the payment was not attributed means the id never made it through.
// Browser: the page with the "Buy" button
const visitorId = window.visitrack?.visitorId?.();

const res = await fetch("/api/checkout", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ priceId: "price_123", visitorId }),
});
window.location.href = (await res.json()).url;

// Server: app/api/checkout/route.ts
const { priceId, visitorId } = await req.json();
const session = await stripe.checkout.sessions.create({
  mode: "subscription",
  line_items: [{ price: priceId, quantity: 1 }],
  success_url: "https://example.com/welcome",
  cancel_url: "https://example.com/pricing",
  client_reference_id: visitorId ?? undefined,
});
return Response.json({ url: session.url });

If you sell through Stripe Payment Links instead of server-created sessions, the same field works as a URL parameter: append ?client_reference_id=<visitor id> to every buy.stripe.com link on your pricing page with a few lines of script. The Stripe setup guide has a copy-paste snippet for both variants.

Don't record revenue from the thank-you page

Firing a “purchase” event from your success page looks easier, and it is wrong in three ways: roughly 30% of users run an ad blocker that can stop the script, people refresh or bookmark the page, and anyone can load the URL without paying. A signed server webhook has none of those failure modes. Use the browser only to carry the id; let the server report the money.

Which field carries the visitor id in each payment provider?

ProviderField for your idPayment eventRefund event
Stripeclient_reference_idcheckout.session.completedcharge.refunded
Lemon Squeezycheckout custom data (custom.visitrack_visitor_id)order_createdorder_refunded
Polarmetadata.visitrack_visitor_idorder.paidrefund.created
Paddlecustom_data.visitrack_visitor_idtransaction.completedadjustment.created
Razorpaynotes.visitrack_visitor_idpayment.capturedrefund.processed
Field and event names as used by VisitTrack's webhook handlers, October 2026. Other tools use the same provider fields under different key names.

Every provider has a free-form slot for your own data, and every one of them echoes it back in the webhook. That is the whole trick. You do not need the provider's own analytics, a tag manager, or a customer data platform to make this work.

Which traffic source gets credit for the payment?

Here is where most setups quietly diverge. Say a visitor reads your blog from a Google search on March 2, comes back from a newsletter link on March 9, and pays on March 11 after typing your URL directly. Three sources touched that customer. Which one made the money?

ModelWho gets the $49What it rewards
First touchGoogle search (March 2)Discovery: the channel that created awareness
Last touchDirect (March 11)Closing: whatever the buyer did right before paying
Last non-direct touchNewsletter (March 9)Closing, but ignoring typed URLs and bookmarks
Linear$16.33 to each of the threeThe whole path, equally
Position based (40/20/40)$19.60 Google, $9.80 newsletter, $19.60 directBoth ends, with a little for the middle
One $49 payment, three touches, five different answers.

For an early SaaS product, our default recommendation is first touch for deciding where to invest and last touch for checking what closes. First touch tells you which channels create customers that would not exist otherwise — that is the question a founder with ten hours a week of marketing time actually has. Last touch is almost always inflated by “direct”, because people who have already decided to buy tend to type the URL or click a bookmark. We go through the trade-offs with worked numbers in first-touch vs last-touch attribution for SaaS.

VisitTrack stores each payment against the visitor's first touch (first referrer, first UTM source/medium/campaign, first landing page) and also records how many days and visits it took to convert. The Revenue view then computes first touch, last touch, linear, time decay and position based side by side from the same sessions, so you can compare models instead of being locked into one. Every model divides the same total, which is the sanity check worth applying to any tool: switching models should move credit between sources, never change how much money there was.

How do you read a revenue-by-source report without fooling yourself?

Once payments start flowing in, the report looks deceptively simple: a list of sources with dollar amounts. Five habits keep it honest.

  • Look at revenue per visitor, not revenue. A source with $600 from 300 visitors ($2.00 per visitor) is a better use of your time than one with $1,500 from 15,000 visitors ($0.10). The revenue per visitor guide explains why this is the metric to sort by.
  • Wait out the conversion lag. If your median days-to-convert is 9, a campaign that launched 5 days ago has not had a chance to pay yet. Judge it after two or three median cycles.
  • Count customers, not just dollars. One annual plan can make a source look like a goldmine. Under 20 conversions, read the number of customers first and the revenue second.
  • Subtract refunds. A source that brings impulse buyers who refund within a week is worse than its gross number suggests. Make sure refunds are matched to the original payment so they reduce the right source.
  • Report the unattributed bucket. If 18% of revenue has no visitor behind it, say so on the slide. Pretending the attributed 82% is 100% is how teams overinvest in whatever happened to be tracked.

Why are some payments unattributed, and how do you fix it?

You will never get to 100%, and a setup that claims to is either guessing or modeling. The realistic target is 80–95% of payments attributed, depending on your audience. Here is where the gap usually comes from.

CauseTypical share of paymentsFix
Ad blockers stop the analytics script, so the visitor never gets an id5–25%, higher for developer audiencesServe the script from your own domain through a first-party proxy
The id is read after redirecting to the provider's hosted pageUp to 100% if wrongRead the id on your own page before the redirect
Testing on localhost, where many trackers ignore hitsAll test paymentsEnable local tracking while testing (data-allow-local in VisitTrack)
Cross-device purchases (read on phone, buy on laptop)5–15% for considered B2B purchasesIdentify signed-in users and send the original first touch at signup
Payments outside Checkout: manual invoices, PaymentIntents you confirm yourselfVariesRoute through Checkout or report the payment from your server with the visitor id
Shares are rough ranges from what we see across sites; your mix depends on audience and checkout flow.

The ad-blocker number deserves context. GWI data reported by Backlinko puts ad blocker use at around 29.5% of internet users worldwide in 2025 and 32.5% in the US, and many blocker lists include analytics domains, not just ads. If you sell to developers, assume the higher end. A first-party proxy (serving the script and the collection endpoint from your own domain) recovers most of that traffic, and the install docs cover how. For the full list of failure states and what each webhook response means, see missing or unattributed payments.

Does revenue attribution work without cookies?

Yes, with one trade-off you should understand before you rely on it. Attribution needs some way to recognize that the person paying today is the person who arrived from a campaign last week. A first-party visitor id stored in the browser does that across days. A fully cookieless setup, where the id is a hash that rotates every 24 hours, can only link a payment to visits from the same day.

There is a clean way around it that keeps the privacy posture intact: the analytics script tells your page where the current visit came from, your own app stores that alongside the signup (in its own database, like any other form field), and sends it back when the person converts. VisitTrack exposes this as window.visitrack.attribution() and accepts the stored first touch on identify or on a signup event; the cookieless mode docs show the full flow. For a B2B product where people sign up on day one and pay on day fourteen, this is the setup we would use.

What about subscriptions and renewals?

Be precise about what is being attributed. A Checkout-based integration records the payment that happens in Checkout — for a subscription, that is the first charge. Renewals are billed later by Stripe Billing as invoices, without your page involved. That is fine for the question “which channel acquires customers”, but it means “revenue by source” is first-payment revenue, not lifetime value.

If you want lifetime value by channel, join the first-touch source to the Stripe customer and compute it in a spreadsheet once a month: total paid per customer from Stripe, grouped by acquisition source from analytics. Feed the result into an LTV calculator per channel and compare it with what each channel costs you through a CAC calculator. For most indie products the ranking of channels does not change much between first payment and LTV, but annual plans and high-churn channels can flip it.

What does a realistic result look like?

Here is an anonymized example of a developer tool at about $3,200 MRR, 90 days after setting up attribution. Monthly plan $19, annual $190.

First-touch sourceVisitorsPaying customersRevenueRevenue per visitor
Google organic (docs and comparison pages)18,40041$1,610$0.088
Hacker News (one Show HN)9,8007$228$0.023
Newsletter sponsorship (utm_medium=sponsorship)1,1509$396$0.344
X (organic posts, t.co referrer)3,3006$209$0.063
ChatGPT and Perplexity referrals6408$437$0.683
Direct / none6,90012$531$0.077
Example data, simplified. Revenue is first-payment revenue over 90 days.

Three conclusions fall out of this that the traffic chart alone would never show. The Show HN that felt like the best week of the year produced the lowest revenue per visitor. The sponsorship that brought only 1,150 visitors was worth roughly four times more per visitor than organic search. And AI assistant referrals, a rounding error in visitor terms, were the single most valuable source per visit — consistent with Ahrefs' 2025 finding that AI search visitors were 0.5% of its traffic but 12.1% of its signups. If you only looked at visitors, you would have doubled down on exactly the wrong channel. For the playbook that turns a table like this into decisions, read which marketing channel brings paying customers.

Should you build this yourself or use a tool?

Building it is genuinely feasible: a visitors table keyed by an anonymous id, a webhook handler that verifies signatures and upserts payments keyed by provider payment id, and a query that groups payments by the visitor's first source. A competent engineer can have a first version in a weekend. The part that takes longer is everything around it — refund matching, deduplication, bot filtering so scraper sessions don't dilute revenue per visitor, five providers' signature schemes, and a UI you will actually open.

If you want it off the shelf, the options as of October 2026 split into two groups. General product analytics suites like PostHog or Mixpanel can do it if you send revenue events yourself and build the reports. Analytics tools with native payment connections — VisitTrack, DataFast and a few others — do the webhook side for you. Google Analytics 4 can ingest Stripe purchases through the Measurement Protocol, but you are responsible for carrying the client id through checkout and for GA4's own consent and sampling constraints; our Google Analytics comparison goes into that in more detail.

How do I see which traffic source generated a Stripe payment?

Pass an anonymous visitor id from your analytics into the Checkout Session as client_reference_id, then match that id when the checkout.session.completed webhook arrives. The payment inherits the referrer, UTM campaign and landing page your analytics recorded for that visitor. Stripe alone cannot tell you, because it never sees the visit that brought the customer.

Can Google Analytics 4 track Stripe revenue by source?

Yes, but only with custom work. You must capture the GA4 client id before checkout, store it with the payment, and send a purchase event to the GA4 Measurement Protocol from your webhook. Visitors who decline consent or block GA never get a client id, so their revenue is missing from the report.

What is client_reference_id in Stripe?

It is a free-form string of up to 200 characters that you set when creating a Checkout Session or append to a Payment Link URL. Stripe stores it on the session and returns it in webhooks, which makes it the standard place to carry your own reference, such as an internal user id or an analytics visitor id.

What percentage of revenue should be attributed?

A healthy setup attributes 80–95% of payments. The rest comes from ad blockers, cross-device purchases and payments made outside your tracked checkout. If you are below 70%, check that the visitor id is read on your own page before the redirect to the payment provider.

Does revenue attribution need a cookie consent banner?

Not necessarily. Attribution only needs a first-party anonymous id, and privacy-first tools that don't use it for advertising or cross-site tracking are often operated without a banner under the audience-measurement exemptions some EU regulators recognize. A fully cookieless mode avoids device storage entirely, at the cost of a 24-hour attribution window unless your app stores the first touch itself. This is general information, not legal advice.

Are subscription renewals attributed to the original source?

With a Checkout-based integration, only the payment made in Checkout is recorded, which for a subscription is the first charge. To see lifetime revenue by source, join the acquisition source to the Stripe customer and sum their invoices separately.

Which attribution model should a small SaaS use?

Start with first touch to decide where to invest, because it shows which channels create customers, and check last touch to see what closes them. Direct traffic usually dominates last touch, so it is a poor guide for channel investment on its own.