Attribution Tracking Breaks Fast When You're Using Fancy Tools

Attribution Tracking Breaks Fast When You’re Using Fancy Tools

Mixpanel and Google Ads: Who’s Actually Claiming Credit?

If you’ve ever tried to line up your Mixpanel conversions with Google Ads’ conversion counts, you’ve probably ended up in a philosophical debate with yourself about “truth” and “reality”. There’s no one version of attribution agreed on by any two platforms. I’ve watched Mixpanel spike in conversions for campaigns Google says barely breathed. And that wasn’t even the weird part—Mixpanel marked all those conversions as direct traffic.

The problem here is that Mixpanel, by default, doesn’t pull UTM tags across SPA navigation unless you’re explicitly handing off query params in your router or using referrer tracking (which breaks on privacy-first browsers half the time). So if Ads brought the traffic, then your frontend swallowed the referral on route change, Mixpanel just counts it as a bounce-to-return visit. Suddenly your Facebook Ads are claiming conversions Google Ads paid for. Meanwhile, your CFO thinks you’re double spending based on the dashboard math.

Honestly, conservatively blaming GA4’s session stitching for 80% of your headaches isn’t even cynical—it’s underestimating the chaos. And just so you know: if you’re tracking attribution windows in Mixpanel shorter than seven days, you’re gonna miss a bunch anyway because of aggressive tab suspending in mobile Chrome on Android.

If everyone claims credit, no one’s lying, they’re just lying at the same time.

Facebook Click IDs and the Infamous fbc/fbp Cookie Timeout Thing

Okay, so if you’re using Facebook’s Meta Pixel with auto advanced matching turned on and still seeing weird gaps in your click conversions… you’re probably getting dunked on by fbc/fbp expiry behavior. The docs kinda hand-wave the whole thing, but here’s the real situation: fbp doesn’t populate if the page loads via a redirect or if third-party cookies are throttled. And fbc, which should persist the actual click ID (like gclid for Google), sometimes disappears between SPA navigation and next-page loads unless it’s refreshed manually in localStorage.

This came up during a push where we were running TikTok and Meta traffic to an Ember app. I spent an entire Friday afternoon tailing Network tab requests trying to figure out why outbound conversions from Meta said 60, but TikTok was logging 120 for almost the same landing flow. It was just a timing issue—the cookies were getting dropped before the pixel fired because the redirect-to-landing page didn’t complete the DOMContentLoaded event fast enough for the script to resolve fbc.

Here’s what actually helped:

  • Manually storing fbc and fbp on first load via query param parsing
  • Delaying pixel firing by 100ms after load
  • Rehydrating the Meta script from localStorage on resume
  • Inspecting _fbp manually before the pixel script initialized (yes, it’s hacky)
  • Flagging summarization errors through a custom attribution checker

Also: even if all this works flawlessly, Safari will still snipe your third-party cookie attribution inside 24 hours unless your pixel lives in a first-party script booted from your backend. Isn’t privacy fun?

UTM Parameters in Paid Email Campaigns: Weird Caveats

UTMs are great, until they aren’t. One extremely cursed interaction happened when we embedded UTMs in SendGrid links for a custom automation we were rolling out. Turned out, some email previews (especially on iOS Mail and certain Outlook web client renderers) click/open the links just to generate safe previews. Nice UX gesture, sure. Terrible for attribution. Those opens counted as user sessions — before the user ever voluntarily interacted.

So when our early conversions from the email campaign started showing up as direct — or worse, paid social — we knew something sketch was happening. We caught it because the landing page logs were showing opens from empty referers with SendGrid IDs but MPA (multi-page apps) were rehydrating mid-session with adblock-safe tracking disabled.

The real kicker? Those ghost openings reset last-click attribution. So when the same user clicked a real ad later, it still resolved to “email”. I now tell clients to add a meta redirect delay on sensitive attribution links — even 250ms helps dodge false beacon triggers.

The Case of Disappearing gclids on AMP Pages

Back when AMP was still haunting us more regularly, I ran into a pretty silly issue where gclid was getting dropped on our Google Ads landing pages. Debugged an inconsistent attribution drop-off in GA4, took apart the source, and boom — the AMP cache was rewriting our URLs. So technically… the real landing was happening inside a Google-hosted proxy, and it wouldn’t forward query parameters reliably depending on the referrer headers. Can’t even make that up.

I tested it on mobile Chrome with the same ad three times. All had gclids. Only the first click actually reached the backend with the gclid intact. Every interaction after was being cached from Google’s side and stripped the querystring unless you force AMP to use canonical URLs. Which we weren’t, because we had some slightly off-spec CSS that broke canonical render fallbacks. Great.

Also, shoutout to the fact that AMP analytics bindings use different trigger timings than standard GA4 tag events. If you’re not explicitly binding your formSubmit or click requests manually, stuff just won’t fire between the cached and original version. Fun times.

If you’re stuck using AMP and want attribution to semi-work:

  • Always set data-amp-addparams to inject missing query params
  • Use hash-based fallback ID tracking (e.g. #glcid=123)
  • PostMessage back to canonical page for server-side associations
  • Absolutely disable JS compression in Tag Manager — it sometimes eats debugging logs

Multi-Touch Attribution Models vs. Last Click in Shopify’s Native Dashboard

I knew something was off when Shopify started claiming influencer traffic was worth more than a Facebook retargeting funnel we’d been nurturing for two months. Turned out their internal reporting model uses first attribution AND doesn’t de-dupe overlapping sessions unless there’s a confirmed purchase in the same browser environment. Which… why.

So when someone taps through an influencer link, leaves, comes back via a Google Shopping ad, and buys? That influencer still gets all the credit unless their session cookie timed out. Meanwhile, if the last click touchpoint is via Safari with ITP blowing away cookies every 24–48 hours, Shopify can’t associate it.

There’s a special place in hell for attribution models that can’t be toggled manually.

I spent a day building a basic attribution parsing layer as a custom Shopify app, using the ‘UTM cleaner’ pattern where we log all initial utms to localStorage and use them as canonical unless the value is explicitly overwritten (based on time decay rules). It’s messy, but way more transparent than their opaque session modeling.

Also: Shopify’s Order object does not track all conversion sources unless sessions persist the full journey. Checkout is a new origin. You lose referer, marketing params, and initiator context unless transferred via postMessage store.

Segment.io: Great Until You Forget to Set `anonymousId` on Server-to-Server Events

This one was hard-earned. We had a pretty clean Segment setup — unified logs, consistent schemas, mapped identities via `userId`. But here’s the sucker punch: if you fire server-to-server events (say, confirmed purchases or subscription activations) and don’t explicitly pass in the `anonymousId` that came from the browser… Segment won’t link them to the original path journey. You’ll see anonymous traffic convert into users, but without clear source-of-truth mapping.

That cascaded into our downstream Customer.io flow marking some subscribers as never having visited the pricing site — even though they did, and we had client-side events. But the upstream Segment pipeline just couldn’t resolve them because `userId` appeared later than the `page` and `track` from browser flow.

You’d think this would be well documented. Nope. Pretty sure someone at Segment just assumes developers intuitively know to pass anonymousId, but we had to reverse-engineer it from our own warehouse logs.

{
  "event": "Purchase Completed",
  "anonymousId": "kl2234x8zv7",
  "userId": "abc_12d_456",
  "properties": {
     "value": 87.00
  }
}

Start passing that consistently or you’ll deal with wrong attribution flow through every downstream connection (Amplitude, Braze, you name it).

Attribution Tools That Ghost On Safari (ITP is the Villain)

If your attribution numbers suddenly look like trash on mobile after a Safari update, you’re not crazy. Intelligent Tracking Prevention (ITP) cuts cookie lifespans down to pretty much nothing unless you do clever workarounds. Many analytics platforms used to rely on third-party cookies or fingerprinting via iframe pings — both disabled or neutered in modern Safari.

An edge case we ran into: PostHog was showing a 0.5% conversion rate for Safari-only customers, even though we knew from internal raw traffic logs that they were completing purchases just fine. Turns out ITP had blocked the POST event to /e endpoint from firing because PostHog inserted it after a zero-delay setTimeout. You can’t delay anything in Safari attribution flow — ITP assumes every millisecond makes you more suspicious.

To fix it, we used a same-domain ingestion endpoint and polyfilled the user agent string manually via a cloud function. Downside? Now PostHog thinks 30% of our users are “Linux ARM Chrome 78,” which is just a generic fallback header we send. But at least the conversions count again.

Similar Posts