How to attribute revenue to marketing channels
How do I know which marketing channel actually made the money?
Attribute revenue to a channel by recording, for every visitor, the source that first brought them, freezing it, and then crediting any later payment from that visitor back to it. Ranking channels by revenue instead of by visitors routinely reorders the list, because the channels that send the most traffic are rarely the ones that send the most buyers.
Why a traffic report ranks channels wrong
A traffic report ranks by volume, and volume and buying intent tend to run in opposite directions. The channels that send the most people are the broad ones — a wide-match ad group, an aggregator link, a thread that did well — and breadth is what dilutes intent. The narrow ones send fewer people who are far more likely to buy.
Say a month brings 41,000 visitors from organic search earning $18,400, and 900 from your newsletter earning $11,200. By visitors the newsletter sits below the fold; by revenue per visitor it is $12.44 against $0.45, a factor of nearly 28. Both rankings are correct. Only one answers the question you opened the dashboard to ask.
What has to be true to credit a payment to a channel
Four things, in this order. Each is a place the chain breaks.
- Capture the acquiring source on the first visit. Read the referring URL and query string the moment a new visitor arrives — referrer, UTM parameters, ad-click ids, landing page.
- Freeze it. Write it to the visitor once and never overwrite it. Skipping this quietly turns the report into a last-touch report: the recorded source becomes whatever the visitor did most recently, which for anyone who came back to buy is usually a direct visit. Choosing between the two models is a real decision — see first-touch vs last-touch — but make it deliberately.
- Tie the payment to the same visitor. The hard step, because the payment happens at your processor, not in the browser. One identifier has to survive the round trip: stamp the visitor id onto the checkout session and read it back off the webhook, or match on an email you already hold for that visitor. Both halves, in code, are in tracking Stripe revenue in your analytics.
- Aggregate on the frozen field.Group payments by the visitor’s first-touch channel, sum, divide by that channel’s visitors.
Steps 1 and 2 need somewhere to keep the frozen value — a first-party cookie, or an id derived server-side. Under a consent regime, a visitor who declines leaves you no cookie to write. A cookieless mode recovers part of that by deriving the id server-side and storing nothing on the device, but a privacy-preserving derivation has to rotate, which shortens the window in which a later payment can still be matched. Whichever route you take, know the size of the hole rather than pretending it away.
What a channel actually is
A channel is a bucket of sources you would make the same decision about — a reporting convenience, not a fact about the world. Nothing in an HTTP request says “organic search”; what arrives is a referring URL and whatever query parameters survived. Classification is a rule over those two, and the rule is where the errors live.
Most implementations converge on roughly this, first match winning:
| Arrives as | Channel | Watch out for |
|---|---|---|
gclid, fbclid or msclkid on the URL | Paid | Present even when the UTMs are not — see below |
utm_medium=cpc, ppc, paid, display | Paid | Only as reliable as whoever tagged the link |
utm_medium=email, or a source naming an email tool | Untagged newsletter links mostly show up as Direct | |
Referrer facebook.com, t.co, linkedin.com, reddit.com | Social | Paid social belongs in Paid — check the click id first |
Referrer google.com, bing.com, duckduckgo.com, no click id | Organic search | An untagged ad click lands here and inflates it |
| Any other referring domain | Referral | Your own domain here means a broken cross-domain setup |
| No referrer and no parameters | Direct | Mostly not typed URLs — see below |
Order matters more than the rows do, and the principle underneath it is that an explicit tag beats an inference: the rules reading a click id or a medium you set yourself run before the ones guessing from a referrer. Paid above social keeps a Facebook ad out of the social bucket; social above referral stops every network being filed as “some site linked to us”. Identical rows in a different order classify the same visit differently.
The paid/organic split is the one to get right
It is the split that moves budget, and the least reliable one, because it rests on tagging a human has to remember. A paid click can arrive with no UTM parameters at all — auto-tagging off, someone edited the ad, a redirect dropped the query string — and because its referrer is google.com it files itself as organic search, under-counting the ads and over-counting SEO in one move. Branded paid search has the opposite problem: it takes credit for demand you already had.
Ad-click ids fix the first one. Google appends gclid, Meta appends fbclid, Microsoft appends msclkid. The platform adds them, not you, so they survive the tagging mistake that lost your UTMs. Capture them at first touch and let their presence decide the channel before any medium is consulted, and paid detection stops depending on anyone’s discipline.
What you cannot engineer away: a click id only helps if your analytics reads it, and Statlark reads those three and no others. So paid traffic from LinkedIn, TikTok, Reddit or an affiliate program separates from their organic only if you set utm_medium yourself. The rule cuts the other way on Meta, which appends fbclid to clicks from ordinary posts as well as ads — so treating a click id as decisive will over-count paid social. If Meta is a big share of your traffic, tag the ads and read the UTM-source breakdown rather than the channel roll-up.
Direct is the honest unknown
Direct means no referrer and no parameters — the analytics equivalent of a shrug. Some of it is what the name implies: typed URLs, bookmarks, customers who know where you live. Much of it lost its referrer in transit — links opened from a native mobile app, a desktop mail client, a messaging app, a PDF. That is dark social and dark email, filed under the wrong name.
Two things follow. You cannot optimise direct, because there is nothing to buy more of, so read its size as a measurement fact rather than a channel doing well. And a jump in direct just after you start something that gets shared privately means the new thing is under-credited. The remedy is tagging the links you control — newsletter, community post, partner page, QR code — with utm_source and utm_medium. Each one you tag leaves the unknown pile permanently.
How Statlark does it
Every visitor carries first-touch columns — referrer, referrer domain, UTM source, medium and campaign, the three ad-click ids, landing path — written on the visit that created the visitor. Later visits only bump the last-seen timestamp, so a return trip cannot overwrite the first touch. Channel is derived from those frozen columns by one rule, first match wins, into six buckets: Ads, Email, Social, Organic, Referral, Direct. Ads is evaluated first, and any of gclid, fbclid or msclkid puts a visit there — so a paid Facebook click classifies as Ads, with the Meta caveat above attached. The full rule is in Reading your data.
Payments attach to that visitor by the first signal that matches: Stripe’s client_reference_id, then session metadata, then the subscription metadata that rides on every invoice a subscription raises, then an email stitch against a visitor who identified themselves. A payment matching none of them is still recorded, as unattributed rather than dropped. Revenue attribution has the checkout code.
The sources report then ranks by revenue rather than visitors, each row carrying visitors, revenue, revenue per visitor and paying conversion, and the same window regroups by channel, UTM source, referrer domain or ad-click platform. Revenue is net of refunds, and a refund is subtracted from the original sale’s date and channel rather than booked as its own event.
# Channels ranked by revenue, not by visitors.
curl "https://app.statlark.com/api/v1/sources?site=sl_xxxx&from=2026-06-01&to=2026-07-01&grouping=channel" \
-H "Authorization: Bearer slk_your_token_here"
# Same window, finer grain: grouping=utm_source | referrer | adclickTwo limits worth stating plainly. A payment in a currency other than the site’s is recorded but excluded from the revenue rollups rather than converted at a rate nobody can audit later, which means a multi-currency business sees channel revenue understated — the reasoning, and what to do about it, is under mixed currencies. And there is no ad-spend import, so no ROAS column: Statlark tells you what a channel earned, you divide by what you paid.
What changes once the ranking flips
The obvious change is budget; the more useful one is what you stop doing. You stop optimising the top of the traffic list, which is where most conversion-rate work goes and where the least money is. You stop treating a channel with 300 visitors as noise. And you start asking the question that resolves an ad decision: what would revenue per visitor have to be for this spend to make sense, and is it?
Two cautions. Revenue is lumpier than traffic, so use longer windows — a channel with four sales in a month has a revenue-per-visitor number a single refund can halve, and comparing two of those over seven days compares two coin flips. And remember what the ranking means: it is first-touch, so it answers “which marketing finds people who go on to buy”, not what deserves credit for the last click. For the per-head metric underneath all of this, see revenue per visitor.
Last reviewed