What PostHog is actually tracking
PostHog captures UTM parameters and referrer data as event and person properties on the first pageview of a session (and, depending on configuration, on the first pageview ever for that person). The web analytics dashboard then buckets sessions into channels using $channel_type - a classification PostHog derives from UTM values, referrer domain, and a set of built-in rules for known ad platforms and search engines.
Attribution "breaking" almost always means one of these properties never got set correctly, not that PostHog failed to record an event.
The diagnostic order that actually works
| Check | What it catches |
|---|---|
| 1. The actual landing URL | UTM parameters stripped by a redirect chain, link shortener, or a CDN/edge rule that rewrites query strings before the page loads |
| 2. Session stitching | A visitor whose session ID changed between the UTM-bearing landing page and the event you are checking - common after a login/identify call that starts a new session under different assumptions |
| 3. Self-referral | Internal navigation (your own domain as the referrer) overwriting a real external referrer because of how the session was defined - check PostHog's self-referral handling rules against current docs, since exact defaults have changed between versions |
| 4. $channel_type classification | A source PostHog's built-in rules do not recognize (a newer ad platform, an unusual UTM medium value) falling into a generic bucket instead of the specific channel you expected |
| 5. Client-side blocking | The same ad-blocker/ITP loss that affects any capture - see the reverse proxy guide if this is the actual root cause |
Implementation done properly
Fixing attribution for good, rather than one broken link at a time, means: standardizing UTM conventions before campaigns launch (so classification does not depend on guessing at medium values), confirming your marketing platform's outbound links are not being rewritten by a redirect service that drops query strings, and deciding explicitly how cross-domain and self-referral traffic should be treated rather than accepting whatever the default does.
How to verify it worked
- Send a test visit through the actual campaign link end to end (ad platform preview or a real click, not a hand-typed URL) and confirm the landing pageview event carries the expected UTM properties.
- Check the session in PostHog's session explorer for the correct
$channel_typeassignment. - Re-pull the web analytics dashboard for that channel after the fix and confirm the volume moved from "none"/miscategorized into the correct bucket - a fix that only looks right in the raw event properties but does not change the dashboard rollup has not actually closed the loop.