A paid social campaign runs for eleven days. The report at the end shows 240 conversions from organic search and almost nothing from paid social, so someone sensible proposes cutting the social budget. The real story is a redirect rule on the apex domain that drops query strings. Every one of those 240 conversions came from the campaign nobody wanted to fund.
A 404 gets caught within the hour, because eventually somebody clicks the ad. A silently stripped utm_medium survives the entire flight, and the wrong conclusion it produces outlives the campaign by a quarter. What follows is the machinery underneath a tracked link, and every place the number goes wrong on the way to your report.
A click is not a session, and both numbers are right
The first argument in every campaign post-mortem is about two numbers that disagree. The link shortener says 10,000 clicks. GA4 says 6,200 sessions. Somebody says the tracking is broken.
It probably isn't. They measure different events.
A click is a redirect service logging an inbound HTTP request and answering with a Location header. That is the entire transaction. It happens at the network edge in about thirty milliseconds, and nothing about it requires a human, a browser, or a rendered page.
A session is a JavaScript tag executing in a real browser on the destination and successfully reaching the analytics collector. That needs the redirect to resolve, the page to load, the tag to fire, the person not to have left, and no content blocker to intervene.
Every item in that second list is a place to lose people. Clicks will always exceed sessions. The gap is information, not a bug — and its composition is worth knowing.
| Cause | Effect | What to do about it |
|---|---|---|
| Email security gateways fetching every URL to sandbox it | Inflates clicks | Report unique visitors, not raw clicks |
| Link-preview crawlers building the little card | Inflates clicks | Filter by user agent |
| Someone taps, then leaves before the tag fires | Reduces sessions | Nothing — real behaviour, and a page-speed signal |
| Content blockers stopping the analytics tag | Reduces sessions | Nothing reliable. A roughly constant tax |
| The chain strips the campaign parameters | Reduces attributed sessions, not total | Fix the chain — see below |
| Two clicks from one person inside the session window | Reduces sessions | Nothing. Expected |
Opens and clicks break differently
These get conflated constantly. Apple's Mail Privacy Protection preloads remote images through a proxy — that is why open rates became fiction. It inflates opens. Click inflation in email comes from a different machine entirely: security gateways that fetch every link in the message before the recipient ever sees it. Same-looking symptom, different cause, different fix. If your email click count spikes and median dwell time on the destination is under a second, you are looking at a scanner, not an audience.
How redirects quietly eat your parameters
Draw the path a click actually takes. Most teams never have, which is exactly why nobody can say which hop broke.
ad platform click tracker
-> https://go.acme.com/spring?utm_source=meta&utm_medium=paid_social
-> 302 -> https://acme.com/spring-sale?utm_source=meta&utm_medium=paid_social
-> 301 -> https://www.acme.com/spring-sale
-> 200 (the tag fires here)Four hops, and the campaign died on the third. The apex-to-www redirect answered with a literal Location that had no query string attached, so by the time the analytics tag executed there was nothing left to read. The tag fires perfectly. It reports a direct visit.
You can check any link you own in about two seconds:
$ curl -sIL 'https://go.acme.com/spring?utm_source=meta&utm_medium=paid_social' \
| grep -i '^HTTP\|^location'
HTTP/2 302
location: https://acme.com/spring-sale?utm_source=meta&utm_medium=paid_social
HTTP/2 301
location: https://www.acme.com/spring-sale
HTTP/2 200There it is on line four. The parameters went in and did not come out.
One caveat: -I sends a HEAD request, and some servers route HEAD differently from GET. If the chain looks suspiciously clean, re-run it as a real GET — curl -sL -o /dev/null -w '%{url_effective}\n' <url>.
A redirect is not obligated to carry your query string. It carries whatever the person who wrote the rule told it to, and the defaults are not on your side.
nginx
# Drops everything after the ? — Location is a literal string
return 301 https://www.acme.com/spring-sale;
# Preserves the query string
return 301 https://www.acme.com/spring-sale$is_args$args;Apache
# Drops the query string
RewriteRule ^/spring$ /spring-sale [R=301,L]
# Keeps it. QSA is "query string append"
RewriteRule ^/spring$ /spring-sale [R=301,L,QSA]Nobody writes the bad version maliciously. It gets written during an HTTPS migration, or a trailing-slash cleanup, by someone solving a genuine problem who had no earthly reason to be thinking about utm_campaign. Then it sits in the config for two years, and every campaign pointed at that path quietly loses its attribution.
The hops you don't control are worse. Geo-redirects from / to /en-gb/. Consent gates. App-install interstitials that only fire on mobile. A partner's click wrapper around your URL. Each is a place a parameter can be dropped by someone who is not you and who will never tell you.
Test the link you are shipping, not the link you built
Paste the final URL — the one with the parameters on it — and follow the whole chain. A destination that works fine when you type it into a browser can still drop your parameters when the campaign version goes through it, because the parameters are the thing being dropped. Checking the bare URL proves nothing.
The referrer is a courtesy; the UTM is a contract
There's a persistent belief that the referrer will save you if the UTMs go missing. It won't, and why not is worth five minutes.
Referer — misspelled in the spec since 1996, and still misspelled — is sent at the browser's discretion, governed by Referrer-Policy. The default in every major browser is now strict-origin-when-cross-origin. Read it literally: cross-origin, you get the origin only. Not the path.
So the referrer on a click from a social post is https://t.co/. Not the post. It tells you the network and nothing else. It cannot tell you the campaign, the creative, the audience or the placement — which is the entire set of things you wanted to know.
And it thins out further:
- An HTTPS page redirecting to an HTTP destination sends no Referer at all — the default policy drops it on a protocol downgrade.
- A site setting Referrer-Policy to no-referrer sends nothing, ever, to anyone.
- A meta-refresh or JavaScript hop makes the interstitial itself the referrer. Your traffic now appears to originate from your own consent gate.
- In-app webviews frequently send no referrer, so much of your social traffic arrives looking like somebody typed the URL from memory.
The referrer is a courtesy extended by the browser, and it is being withdrawn a little more with every release. The UTM is a contract you wrote into the URL yourself. That is the whole reason the convention exists — and it is why parameters surviving the redirect chain isn't a detail. It's the product.
Naming is most of the job
Everything above is a one-time fix. Naming is forever, and it's where most tracking data actually dies.
UTM values are case-sensitive strings, stored exactly as you typed them. Facebook, facebook and FaceBook are three values occupying three rows in your source report. Nothing warns you. Multiply that by a utm_campaign four people spelled four ways — spring_sale, spring-sale, Spring Sale 2026, springsale26 — and the campaign that actually worked is four small rows that each look like a rounding error.
| Parameter | The question it answers | Good | Avoid |
|---|---|---|---|
| utm_source | Which property sent the click? | newsletter, meta, partner_acme | email (that is a medium), Facebook (case) |
| utm_medium | What kind of marketing is this? | email, cpc, paid_social, affiliate | newsletter_june (that is a campaign) |
| utm_campaign | Which initiative does it belong to? | spring_sale_2026 | test, final_v2, a bare date |
| utm_content | Which creative or slot within the campaign? | hero_cta, footer_link, variant_b | Leaving it empty when two links point at one page |
| utm_term | Which keyword? Paid search only. | link_tracking_tool | Repurposing it as a fifth freeform field |
Five rules cover almost all of it:
- Lowercase everything — not a style preference, but the only way two people reliably produce the same string.
- One separator. Underscores or hyphens, never both, never a space.
- Medium comes from a closed list. If someone needs an eleventh value, that is a conversation, not a text field.
- Source is the property, medium is the mechanism: source=newsletter and medium=email, never the reverse.
- Never put a UTM on an internal link. It starts a new session and overwrites the original acquisition source — you will have laundered your own paid traffic into internal_promo, and you will never get it back.
None of this survives as a document. A naming convention in a wiki is a naming convention that is already out of date, because the actual mechanism by which links get built is somebody copying last month's URL and editing it in the browser bar. It survives as a builder that won't let you type the wrong thing: a fixed dropdown for utm_medium, forced lowercasing, a validator that rejects a space before the link exists. That's the part TrackLink is built to do — and it's also the part you can approximate with a locked spreadsheet on day one. Either works. A wiki page does not.
The compounding cost of getting this wrong, worked through with actual arithmetic, is in how broken links and bad UTMs waste paid media spend.
Where clicks become conversions
Here is the mechanic that makes broken tracking worse than no tracking at all.
GA4, like Universal Analytics before it, defaults to last non-direct click attribution. If a session arrives carrying no campaign information, it is not filed under direct. The model looks back across the lookback window, finds the most recent non-direct source it already knows for that user, and credits that instead.
Follow it through. Your paid social link loses its parameters at the apex redirect. The session arrives clean, with nothing to identify it. That user first found you through a Google search three weeks ago. Last non-direct fires. Organic search takes the conversion.
Your broken campaign doesn't show up as a hole in the report. It shows up as another channel's success story. That's why the failure is so durable — there's no gap to notice, and the wrong channel is now posting numbers good enough to defend a budget increase.
Which raises the second thing worth knowing about short links: they get blamed for exactly this, and they are usually innocent. A redirect hop is invisible to attribution as long as the parameters survive it. The redirect isn't the problem — the parameters not surviving it is. Those get conflated constantly, normally in a meeting where somebody proposes banning short links, and the domain that redirect runs on turns out to matter for reasons that have nothing to do with attribution.
The other half of the gap: the conversion is usually recorded somewhere that has never heard of your UTM — a payment processor, a CRM, a backend job firing four hours later. Closing that loop means carrying an identifier from the click through to the thing that made money, which is what conversion tracking and revenue attribution are for, whether through an existing integration or a webhook you post yourself.
Instrument before you launch
Ten minutes here beats any dashboard you build afterwards.
Before the campaign goes live
- Follow the chain on the final URL, parameters attached, and confirm they survive to the 200.
- Confirm the destination isn't a soft 404 — a page rendering 'product not found' with a 200 status is invisible to every monitor you own.
- Run the chain on mobile too. Interstitials and geo-redirects often fire on only one.
- Confirm the tag fires with the parameters attached, not just on the bare URL.
- Fire one real test conversion end to end and find it in the report before you spend money.
- Lowercase every value, and confirm the medium came from your closed list.
- Check the link renders intact where it will be pasted. A URL truncated by a client is a different URL.
- Point a monitor at the destination, alerting somewhere a human will see it mid-flight.
What to watch while it runs
Pre-launch checks catch launch bugs. They do nothing about the thing that breaks on day six, which is where most of the money goes.
Destinations rot mid-flight. A product sells out and the CMS unpublishes the page. A developer ships a redirect on Thursday afternoon. A certificate expires. None of this is visible from inside the ad platform, which will go on spending your money at precisely the same rate into a page that no longer works.
So monitor the destination, not just the link. Two questions, every few minutes:
- Does the final URL still return 200 — and is it the URL you expected? Same host, same path, parameters intact.
- Has the chain changed shape since you last looked?
The second question catches what the first cannot, and it is the highest-signal alert in campaign tracking. A chain that was two hops and is now three still returns a perfectly healthy 200. It just does it after passing through something new that may or may not have carried your parameters along. Nobody told you. Somebody changed something. Continuous destination and redirect-chain monitoring exists for that gap specifically, because it is not something a human catches by clicking the ad once a day.
The most expensive campaign bug is the one that returns a perfectly healthy 200.
The short version
Campaign tracking is three separate jobs that get discussed as one.
- Delivery — the click reaches the right page, through however many hops, parameters intact. A plumbing problem, testable with curl in an afternoon.
- Naming — everyone who builds a link produces the same string for the same thing. A governance problem, only ever solved by tooling that constrains the input.
- Attribution — the click connects to the money. A modelling problem, and the only one of the three that is genuinely hard.
Most teams spend all their energy on the third while losing their data to the first two. Fix delivery and naming — both unglamorous, both finite, both largely done in a day — and the modelling problem becomes something you can have a real argument about, rather than an argument about whether the numbers mean anything at all.
Make the numbers trustworthy first. Then argue about what they say.
Check a link before you ship it.
Paste any URL and get a readiness score, the full redirect chain, UTM detection and a social preview. No signup.
Get the next one by email
Roughly monthly. No filler.