Skip to content

Click ID Preservation

Click IDs (gclid, fbclid, msclkid, ttclid) are how each ad platform reconciles a paid click with a downstream conversion. They are only present on the first landing URL. If the user clicks through to a payment processor, authenticates on a second domain, or simply navigates around the site for ten minutes, the original query string is long gone — and so is the ad-platform match.

This recipe captures each click ID at the first opportunity, stores it in a first-party cookie with a long expiry, and re-attaches it to the conversion event (client-side, server-side, or both).

A user clicks a Google Ads search result carrying ?gclid=Cj0KCQ... and lands on /pricing. They browse for a minute, click through to /checkout, get redirected to the payment processor, come back on /thank-you, and the purchase event fires.

At the point the purchase fires, location.search contains ?session=xyz (or nothing). gclid is long gone. Without it, enhanced conversions cannot match the purchase back to the ad click, and your Google Ads ROAS dashboard under-reports the campaign.

The same pattern breaks Meta (fbclid), Microsoft (msclkid), and TikTok (ttclid) attribution.

  1. Capture all known click IDs on every page load.
  2. Store each one in a first-party cookie with a long expiry (90 days is the common window).
  3. At conversion time, read the cookie(s) and attach the values as event parameters or user properties.
  4. Forward to ad platforms via the GA4 tag, Google Ads tag, Meta CAPI, or sGTM.

Create a Custom HTML tag that fires on All Pages with a low numeric priority (so it runs before any conversion tag).

<script>
(function () {
var CLICK_IDS = ['gclid', 'fbclid', 'msclkid', 'ttclid', 'dclid', 'wbraid', 'gbraid'];
var params = new URLSearchParams(window.location.search);
var maxAge = 60 * 60 * 24 * 90; // 90 days
CLICK_IDS.forEach(function (key) {
var value = params.get(key);
if (!value) return;
document.cookie =
'_cid_' + key + '=' + encodeURIComponent(value) +
'; Max-Age=' + maxAge +
'; Path=/; SameSite=Lax; Secure';
});
})();
</script>

Only overwrite when a new value is present on the URL — the if (!value) return guard keeps older click IDs intact when the user navigates internally.

Create a First-Party Cookie variable in GTM for each click ID you capture:

  • Cookie - gclid → cookie name _cid_gclid
  • Cookie - fbclid → cookie name _cid_fbclid
  • Cookie - msclkid → cookie name _cid_msclkid
  • Cookie - ttclid → cookie name _cid_ttclid

Tick URI-decode cookie so values match the original.

Add each click ID as an event parameter on your purchase / lead tag:

  • gclid{{Cookie - gclid}}
  • fbclid{{Cookie - fbclid}}
  • msclkid{{Cookie - msclkid}}
  • ttclid{{Cookie - ttclid}}

Register them as custom dimensions in GA4 if you want them to appear in Explorations and Data Studio.

Step 4 — Server-side continuation with sGTM

Section titled “Step 4 — Server-side continuation with sGTM”

Browser cookies written from JavaScript get truncated to 7 days under Safari ITP. For click IDs to survive a 90-day window, rewrite them as first-party HTTP cookies from sGTM.

  1. Route the GA4 stream through sGTM on a first-party subdomain (e.g. sgtm.example.com) — see Cloudflare Proxy Setup.
  2. In sGTM, create a Cookie variable reading the click ID from the incoming request.
  3. Create a Set-Cookie response modifier that re-writes the cookie with HttpOnly=false; Max-Age=7776000; SameSite=Lax. Since sGTM responses come from your own domain, the cookie gets the full browser retention rather than the 7-day JS ceiling.
  4. Read the cookie on the server side when building outbound Google Ads / Meta CAPI / Microsoft Ads UET events.

Users who leave for PayPal, Klarna, Stripe Checkout, or a bank 3-D Secure flow come back with the payment provider as referrer and no query string from the original ad click.

Because we captured the click IDs on first landing and stored them in a 90-day first-party cookie, the conversion tag on /thank-you still sees them. The only remaining risk is the browser clearing the cookie during the round-trip — mitigated by the sGTM rewrite in Step 4.

Pair this with the stored-client-id technique from Payment Redirect Attribution Fix for full coverage.

  1. Open an incognito window and visit https://example.com/?gclid=TEST_CLICK_123.
  2. In DevTools → Application → Cookies, confirm _cid_gclid=TEST_CLICK_123 is set with a 90-day expiry.
  3. Navigate away and back. Confirm the cookie is unchanged.
  4. Complete a conversion. In GA4 DebugView, verify the purchase event carries gclid=TEST_CLICK_123.
  5. Repeat with ?fbclid=..., ?msclkid=..., ?ttclid=....
  6. Repeat in Safari to confirm the sGTM-written cookie survives ITP’s 7-day JS cookie cap.

Overwriting good click IDs with empty values. If you drop the if (!value) return guard, every internal page view blanks the cookie. Always short-circuit when the URL has no click ID.

Click ID leakage across users on shared devices. A 90-day cookie will attach the first user’s gclid to the second user’s conversion. Clear the click ID cookie inside your logout handler, and always overwrite when a fresh click ID arrives.

Safari ITP truncates JS-written cookies to 7 days. If you have long consideration cycles, the server-side rewrite in Step 4 is not optional. Without it, roughly one in three Safari users will drop out of the 90-day window.

wbraid and gbraid require different handling. These are iOS app-to-web / app-to-app click IDs. Capture them in the same cookie pattern, but never forward them as gclid — Google Ads rejects mixed IDs.

Consent Mode denies storage. If ad_storage=denied, you cannot write the cookie. Google’s Consent Mode v2 will still transmit a hashed click signal, but it is not a substitute for the cookie path.