Skip to content

Deduplication & transactionId

How Trackbridge identifies conversions for deduplication and attribution. These rules are surfaced in the user-facing quick start; this page captures the reasoning so future changes preserve the intent.

The two identifiers, doing two different jobs

Section titled “The two identifiers, doing two different jobs”
IdentifierJobSourceRequired for dual-send
transactionIdDedup key — tells Google “client and server events are the same conversion”User-supplied (or auto-generated)Yes
gclid / gbraid / wbraidAttribution key — tells Google which ad click drove the conversionURL query params on landingNo (but conversions won’t attribute without it)

These do different things. transactionId matches client and server events to each other. Click identifiers match the conversion to the original ad click. You almost always want both.

  1. If you pass transactionId, Trackbridge uses it verbatim. No normalization, no transformation. Google’s matching is exact-string.

  2. If you do NOT pass transactionId, the SDK auto-generates a UUID v4 prefixed with tb_ (e.g., tb_a8f3c1d2-...) and uses it for the current call.

  3. Auto-generation disables dual-send for that call. This is the most opinionated decision in the SDK and it is intentional. Reasoning:

    • If both sides auto-generate, they generate different UUIDs.
    • Google would see two different transactionIds and count two conversions.
    • Silent double-counting is worse than not dual-sending.
    • You can fix this by passing a transactionId you control — usually your order ID.
  4. When dual-send is disabled, the SDK emits a loud warning linking back to this page:

    [trackbridge] ⚠️ trackConversion called without transactionId
    → Auto-generated: tb_a8f3c1...
    → Dual-send disabled for this call. Pass a transactionId you control
    to enable cross-side dedup.
    → See: trackbridge.dev/docs/dedup

Click identifier rules (gclid / gbraid / wbraid)

Section titled “Click identifier rules (gclid / gbraid / wbraid)”

Three flavors, same behavior:

  • gclid — standard Google Ads click ID for web traffic
  • gbraid — iOS app campaigns (replaces gclid in ATT-restricted environments)
  • wbraid — web equivalent for ATT-restricted scenarios

Trackbridge supports all three from v1. They’re handled identically in the SDK; only one will typically be set per user.

On createBrowserTracker init:

  1. Read URL query params for gclid, gbraid, wbraid
  2. If any are present and consent permits, write to first-party cookies:
    • _tb_gclid, _tb_gbraid, _tb_wbraid
    • Secure, SameSite=Lax, Path=/, host-only by default
    • 90-day expiry (matches Google’s default attribution window)
  3. On every trackConversion / trackEvent call, read from cookies and attach automatically

You do nothing on the browser side — it just works.

The server cannot capture click IDs from the URL. You must:

  1. Read them on the browser via tracker.getClickIdentifiers() (returns { gclid, gbraid, wbraid })
  2. Send them to your server (form post, API call, whatever)
  3. Persist them with the order/lead in your database
  4. Pass them to serverTracker.trackConversion() at conversion time

There is no magic here — it’s your responsibility to wire up the persistence.

clickIdentifierStorage init flag:

  • 'cookie' (default) — persistent across sessions, survives page reloads
  • 'memory' — captured from URL but not persisted; useful when you manage your own storage
  • 'none' — no automatic capture or storage; pass click IDs explicitly

The interaction between consent and click identifiers is the trickiest correctness problem in v1.

[init]
├─→ consent unknown → hold values in memory, defer cookie write
│ ├─→ updateConsent grants ad_storage → write cookies
│ └─→ updateConsent denies ad_storage → keep memory only, no cookies
├─→ consent already denied → memory only, no cookies, never write
└─→ consent already granted → write cookies immediately
  1. The SDK never writes _tb_* cookies when ad_storage is denied. Period.
  2. If consent is unknown at init, the SDK captures URL params into memory but defers the cookie write. It subscribes to consent updates from your CMP via the gtag consent API and writes cookies if/when granted.
  3. If consent is denied for the entire session, click IDs live in memory only and disappear when the user closes the tab. This is the correct behavior under GDPR / Consent Mode v2 — losing attribution is the price of respecting consent.
  4. updateConsent is your mechanism to drive this. Call it from your CMP’s grant/deny callbacks.

The 24-hour dedup window (out of scope for the SDK)

Section titled “The 24-hour dedup window (out of scope for the SDK)”

Google’s dedup window for matching server-uploaded conversions against gtag-fired ones is approximately 24 hours. After that, server-side conversions fired with a matching transactionId will NOT dedupe — they’ll be counted as separate conversions.

This rare-but-real failure mode (e.g., delayed Stripe webhook retries) cannot be solved at the SDK level. The SDK does not try to be clever about it. The Trackbridge Dashboard (post-v1) will surface this — “37 conversions fired server-side >24h after the matching client event; these may be double-counted” — because only the dashboard has visibility into both timestamps. Until then, document the limit and move on.