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”| Identifier | Job | Source | Required for dual-send |
|---|---|---|---|
transactionId | Dedup key — tells Google “client and server events are the same conversion” | User-supplied (or auto-generated) | Yes |
gclid / gbraid / wbraid | Attribution key — tells Google which ad click drove the conversion | URL query params on landing | No (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.
transactionId rules
Section titled “transactionId rules”-
If you pass
transactionId, Trackbridge uses it verbatim. No normalization, no transformation. Google’s matching is exact-string. -
If you do NOT pass
transactionId, the SDK auto-generates a UUID v4 prefixed withtb_(e.g.,tb_a8f3c1d2-...) and uses it for the current call. -
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
transactionIdyou control — usually your order ID.
-
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 controlto 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 trafficgbraid— iOS app campaigns (replacesgclidin 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.
Browser side: automatic capture
Section titled “Browser side: automatic capture”On createBrowserTracker init:
- Read URL query params for
gclid,gbraid,wbraid - If any are present and consent permits, write to first-party cookies:
_tb_gclid,_tb_gbraid,_tb_wbraidSecure,SameSite=Lax,Path=/, host-only by default- 90-day expiry (matches Google’s default attribution window)
- On every
trackConversion/trackEventcall, read from cookies and attach automatically
You do nothing on the browser side — it just works.
Server side: explicit pass-through
Section titled “Server side: explicit pass-through”The server cannot capture click IDs from the URL. You must:
- Read them on the browser via
tracker.getClickIdentifiers()(returns{ gclid, gbraid, wbraid }) - Send them to your server (form post, API call, whatever)
- Persist them with the order/lead in your database
- Pass them to
serverTracker.trackConversion()at conversion time
There is no magic here — it’s your responsibility to wire up the persistence.
Storage modes
Section titled “Storage modes”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
Consent Mode v2 interaction
Section titled “Consent Mode v2 interaction”The interaction between consent and click identifiers is the trickiest correctness problem in v1.
State machine
Section titled “State machine”[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- The SDK never writes
_tb_*cookies whenad_storageis denied. Period. - 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.
- 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.
updateConsentis 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.