Docs · UTM · Monitoring

How to Monitor All Your Campaign UTM Tags and Fix Issues

Build a lightweight audit loop: baseline export, weekly anomaly checks, redirect tests, and fixes for the failures that quietly turn paid and email traffic into Direct.

· Updated

UTM failures don’t throw errors. A redirect that drops the query string, someone typing Email instead of email, an affiliate inventing a campaign name — you notice weeks later when a report doesn’t add up and the raw data can’t be rewritten.

Treat tagging like production: scheduled checks that catch drift in days, plus known fixes per failure mode. Naming and channel rules live in how to optimize UTM tags; Google Ads placement (template vs Final URL suffix) is in the Ideal tracking-template setup.

Baseline audit first

You can’t spot drift until you know what “correct” looks like today.

1. Export every distinct combination

In GA4, explore Session source / medium / campaign / manual ad content with Sessions, last 12 months, export. BigQuery is cleaner if you have it:

SELECT
  collected_traffic_source.manual_source AS source,
  collected_traffic_source.manual_medium AS medium,
  collected_traffic_source.manual_campaign_name AS campaign,
  COUNT(*) AS events
FROM `project.analytics_XXXXXX.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20250101' AND '20260101'
GROUP BY 1, 2, 3
ORDER BY events DESC

2. Classify each row

Valid, casing variant of valid, typo, unknown, or empty. Rank by volume — fix high-session defects first; ignore rare one-offs until the top of the list is clean.

3. Quantify damage

Sum Unassigned channel sessions plus (direct) / (none) where you expected a campaign. That’s the number that justifies the fix work.

Six checks worth running continuously

1. Unassigned and (not set)

Track Unassigned as a % of sessions weekly. Healthy accounts sit near zero. A sustained rise usually means a new campaign shipped with a medium GA4 can’t classify. Treat Unassigned > 1% of sessions as an incident.

2. New value detection

Highest leverage. Each week, list source / medium / campaign combos that appeared for the first time in the last 7 days. Approve real launches; catch typos while the campaign is still live.

Automate with a scheduled BigQuery compare against a registry of approved values and mail anything unmatched.

3. Casing and whitespace

Pure defects — no legitimate reason for mixed case or padded values:

SELECT source, medium, campaign, COUNT(*) AS c
FROM base
WHERE LOWER(medium) != medium
   OR LOWER(source) != source
   OR TRIM(campaign) != campaign
   OR campaign LIKE '%20%'
GROUP BY 1, 2, 3
ORDER BY c DESC

4. Medium against your allowlist

Compare observed utm_medium to your controlled vocabulary. This is what catches paidsearch, newsletter, Social, banner, cpc-remarketing and every near-miss people invent.

5. Incomplete parameter sets

Flag sessions with utm_source but empty utm_medium, or paid traffic missing utm_campaign. Partial tags look fine at a glance and poison channel reports.

6. Live link validation

Tags can be perfect in the ad platform and still die on your site. Weekly cron: request each standard landing URL with known test params, follow redirects, assert the final URL still has them.

curl -sIL "https://example.com/landing?utm_source=test&utm_medium=test&utm_campaign=test" \
  | grep -i "^location:"

Inspect the final location for parameter survival. This is the check almost nobody runs — and it catches the most expensive silent failure.

Platform-side checks (analytics is downstream)

Generate known-good links in the Campaign URL builder or the tracking template builder so the registry and the live URL stay in sync.

Fixing the usual failures

Casing splits one campaign into several. Fix forward in the link builder (lowercase everywhere). You can’t rewrite GA4’s collected dimensions — consolidate in BigQuery with LOWER(), Looker Studio calculated fields, or custom channel groups that match case-insensitively.

Wrong channel. Check utm_medium against GA4’s rules, correct the tag, and use a custom channel group as a bridge while corrected traffic rolls in.

Redirects strip parameters. Trailing-slash normalisation, HTTP→HTTPS drops, locale redirects, CDN rules. Preserve the query string server-side ($query_string in nginx, QSA in Apache), then re-run link validation.

Untagged channels. Inventory paid search, paid social, organic social, email, affiliates, PDFs, QR codes, signatures, push, partner listings. Each untagged surface inflates Direct or Referral.

Internal links with UTMs. Crawl your own hrefs for utm_. Every hit restarts the session and steals acquisition credit — remove them; use GA4 events for on-site promos.

Malformed URLs. Double ?, double &, missing =, blind concat onto URLs that already had a query string. Builders beat hand-typed strings.

Registry over alerts alone

Alerts say something changed. A registry says whether it was intended. One row per live link: full URL, each parameter, owner, channel, launch date, purpose. New-value detection becomes trivial — anything in analytics not in the registry is either unlogged or wrong.

Cadence that holds

FAQ

Can I fix historical UTM data in GA4?

Not the raw dimensions. Compensate in reporting (custom channel groups, Looker Studio, BigQuery). Prevention is the real fix.

Why is paid traffic showing as Direct?

Usually redirects stripping the query string, or a malformed landing URL. Request the tagged link and follow redirects.

What’s an acceptable Unassigned level?

Under 1% of sessions. Sustained above that means active tagging problems.

How do I find UTMs on internal links?

Crawl with any SEO crawler; search extracted internal links for utm_. Fix every hit.

Do I need BigQuery?

No — a monthly GA4 distinct source/medium/campaign export covers most value. BigQuery just makes the weekly checks cheap to automate.

Which single check first?

Live link validation. It catches the failure careful tagging can’t prevent: perfect tags destroyed by your own redirects.

Related

← All documentation