---
name: implement-transaction-id-deduplication
description: >-
  Stops conversion inflation from page refreshes, back-button revisits, and multi-fire tags by
  ensuring every conversion event carries a unique transaction identifier that Google Ads
  deduplicates on. The identifier plumbing (order id on ecommerce, generated submission id on lead
  forms, data-layer and tag field mapping) is developer/GTM work; VigilDog quantifies the problem and
  proves the fix, comparing Google Ads counts to backend counts via GAQL before and after, and
  monitoring for drift monthly. Run it with every new conversion action and whenever Ads counts
  outrun backend counts. It is also the enabler for configure-conversion-adjustments, which matches
  on these ids. Not for correcting values (configure-conversion-adjustments), not for excluding
  already-corrupted periods (set-up-data-exclusions), and not the base tag build
  (set-up-google-ads-conversion-tracking).
---
# Transaction ID Deduplication

## Purpose
Give every conversion a unique identifier so repeat fires of the same real-world event collapse to
one recorded conversion. Without it, a refreshed thank-you page is a new sale as far as bidding is
concerned, and inflated counts quietly mis-train Smart Bidding and misstate performance. The ids
also unlock post-sale corrections, since adjustments match on them.

## When to run
- Any new conversion action is being created (dedup should ship with it, not after it).
- Google Ads conversion counts consistently exceed backend order/lead counts.
- The confirmation page is refreshable or revisitable (nearly always true).
- Multiple tag systems fire on the same event (GTM plus platform plugin plus gtag).
- Before configure-conversion-adjustments, adjustments cannot match without ids.

## When NOT to run
- The count problem is a period-bounded inflation incident already ended, set-up-data-exclusions for cleanup (and this skill to prevent recurrence).
- Values (not counts) are wrong, configure-conversion-adjustments.
- No conversion action exists, set-up-google-ads-conversion-tracking first.

## Prerequisites
- Active conversion action(s) with tags firing.
- A unique-id source per conversion type: platform order id for purchases; a server-generated
  submission id for lead forms (CRMs often mint one); call-tracking id for calls.
- Data-layer / tag access to carry the id from backend to tag.
- Backend export capability for count reconciliation.

## Procedure
1. Quantify the inflation first with VigilDog: `gads_run_gaql_query` for the last 28 days of
   conversions per action per day; user supplies backend counts for the same window. Compute the
   overcount per action. This is the before picture and the priority order, fix the most
   inflated actions first.
2. Map an id source per conversion type with the user:
   - Purchases: the platform's order id (unique per order by construction; confirm it is exposed
     on the confirmation page).
   - Lead forms: no natural id, so the backend must mint one per submission, a UUID or CRM
     record id. Never a timestamp alone (collides) and never the user's email or phone (one user,
     many submissions, email is an identity, not an event id).
   - Calls / app installs: the tracking platform's per-event id.
3. HUMAN STEP (outside VigilDog): the developer pushes the id into the conversion event's data
   layer (or gtag parameter), generated server-side and available before the tag evaluates.
4. HUMAN STEP (outside VigilDog): map the id in the tag layer, a data-layer variable feeding the
   transaction-id field on every Google Ads conversion tag (all types: purchase, lead, call).
   Same variable name convention across tags keeps this maintainable. Publish.
5. HUMAN STEP (outside VigilDog): validate in GTM preview across 3-5 test conversions: id present
   (never blank/undefined), unique per event, equal to the backend record's id. Then walk every
   conversion path, desktop, mobile, express-pay checkouts, guest vs logged-in, each form type, because alternate flows are where blanks hide.
6. HUMAN STEP (outside VigilDog): duplicate-fire test, complete a test conversion, refresh the
   confirmation page so the tag fires again with the same id.
7. Verify dedup with VigilDog ~24 h later: `gads_run_gaql_query` on the test window; the doubled
   fire must appear as a single conversion. Two recorded → the id is not reaching the tag; back
   to step 4.
8. Reconcile at day 7: rerun the step-1 comparison. Google Ads counts should now sit at or just
   below backend counts (attribution windows explain small gaps). Report the before/after delta.
9. Install the drift check: add a monthly Ads-vs-backend count comparison (same GAQL) to the
   account's review rhythm; creeping overcount later means a new untagged path appeared.

## Decision rules
- A blank transaction id is not neutral: every fire counts separately. Blanks found on any path
  are a stop-and-fix, not a note.
- Id requirements: unique per event, stable across retransmissions of the same event, generated
  server-side before the data-layer push. Timestamp+random or UUID for synthetic ids.
- Never derive the id from user identity (email/phone/customer id), repeat business would be
  deduplicated away.
- Cross-device refires of the same id deduplicate too, that is a feature; use the same id GA4
  receives so cross-platform reconciliation stays possible.
- Post-fix expectation: Ads counts ≤ backend counts, gap explained by attribution windows. Ads
  counts still above backend → some path still fires without an id.
- Historical inflation is not repaired by the fix; if a badly inflated period will distort
  Smart Bidding, bracket it via set-up-data-exclusions.

Reconciliation reading guide (step 8/9 outcomes):
- Ads ≈ backend (within a few percent) → healthy; the residual is attribution-window timing.
- Ads noticeably below backend → not a dedup problem; that is attribution loss territory
  (implement-enhanced-conversions or implement-server-side-tagging).
- Ads still above backend after the fix → an untagged or unfixed fire path remains; enumerate
  paths again rather than tolerating "close enough".
- Drift appearing months later → a new form, checkout flow, or replatform shipped without the
  id; the monthly check is the tripwire, and the fix is repeating steps 3-5 for the new path.

## Common failure modes
- Data-layer push racing the tag: id generated late, tag reads undefined. Server-side generation
  before render fixes the ordering.
- Express-pay and other alternate checkout flows skipping the data-layer entirely, the classic
  partial-coverage leak; test every path, not the happy path.
- Timestamp-only ids colliding under load.
- Variable-name mismatch between the data-layer key and the GTM variable, a one-character typo
  yields permanent blanks.
- Fixing purchases and forgetting lead forms and calls; dedup is per tag, and each tag needs the
  field mapped.
- Reusing order ids across environments (staging orders leaking into production tags) so test
  traffic deduplicates against real conversions, keep test id namespaces distinct.
- Assuming a platform plugin "handles it" without verifying the id actually reaches the Google
  Ads tag; plugins frequently populate their own analytics but not the Ads transaction field.

## Related skills
- Run with: set-up-google-ads-conversion-tracking (dedup ships with the base build).
- Enables: configure-conversion-adjustments (matches on these ids),
  set-up-cart-data-and-profit-tracking and set-up-new-customer-tracking (enrich the same event).
- Cleanup partner: set-up-data-exclusions (historical inflated periods).
- Ongoing: run-a-monthly-performance-review (the monthly drift check lives there).
- If deflated post-fix numbers surprise stakeholders: the "drop" is the inflation leaving the
  data, not lost performance, brief them with the step-1 before/after table, and recalibrate
  CPA/ROAS baselines in the next monthly review rather than reverting anything.
