Tutorial
Set up conversion tracking that survives
Conversion tracking breaks quietly: a redesign changes a button's DOM structure, consent mode gets misconfigured, or two tags fire the same event twice. This walks through building tracking that keeps working after the next site change, and setting realistic expectations for cross-platform numbers.
- Intermediate
- 14 minute read
- Last reviewed 2026-08-10
Most conversion tracking is set up once, during launch, and never documented. Six months later nobody knows which events are real conversions, which are duplicates, and why the ad platform reports twice as many leads as GA4. That gap is not usually a bug. It is measurement methodology.
The fix is a written event taxonomy, a small number of true key events, explicit deduplication logic when both client and server side tracking exist, and consent handling that does not silently drop data for a large share of visitors.
Before you start
- A GA4 property already connected to the site
- Access to Google Tag Manager or direct template edits
- Basic familiarity with dataLayer pushes and gtag syntax
Steps
Work through it in order
Step 1
Write the event taxonomy before touching any tags
List every meaningful user action, its trigger condition, and its parameters in a shared document before implementing anything. Without this, different team members name the same action differently across GA4, the ad platform, and any CRM integration, and reconciliation becomes guesswork.
Event taxonomy (excerpt) event_name trigger key_params form_submit_lead contact form success response form_id, page_path call_click tel: link click page_path quote_request multi-step form final submit service_type, lead_value scroll_75 75% scroll depth on any page page_pathStep 2
Fire events through dataLayer, not inline onclick handlers
Pushing to dataLayer keeps tracking logic out of your application code and lets Tag Manager own trigger conditions. This also means a marketing team member can adjust triggers without a code deploy, which matters once the taxonomy grows past a handful of events.
dataLayer push on form success document.querySelector("#contact-form").addEventListener("submit", () => { window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "form_submit_lead", form_id: "contact-form", page_path: window.location.pathname }); });Step 3
Mark true key events in GA4, sparingly
GA4 lets you flag any event as a key event (formerly 'conversion'). Flag only actions that represent real business value: a submitted lead form, a booked call, a completed purchase. Marking scroll depth or outbound clicks as key events dilutes reporting and confuses anyone reading it later.
Send the event to GA4 via gtag gtag("event", "form_submit_lead", { form_id: "contact-form", page_path: window.location.pathname, value: 1, currency: "USD" });Step 4
Deduplicate when server side events exist alongside client side
If you send the same conversion from both the browser and a server via the Measurement Protocol, GA4 needs a shared identifier to avoid double counting. Generate one transaction or event ID at the point of the action and pass it through both paths.
Shared event ID for deduplication // client side const eventId = crypto.randomUUID(); gtag("event", "form_submit_lead", { form_id: "contact-form" }); fetch("/api/track-server-side", { method: "POST", body: JSON.stringify({ eventId, event: "form_submit_lead" }) }); // server side, sent via Measurement Protocol, reusing the same eventId // so GA4's deduplication window can match and discard the duplicateStep 5
Send server side events for actions the browser cannot see reliably
Ad blockers, tracking prevention, and third party cookie restrictions cause client side tags to under-report. For high value actions like a closed sale confirmed later in a CRM, send the event server side directly to GA4's Measurement Protocol, keyed to the client_id captured at the original visit.
Server side Measurement Protocol call POST https://www.google-analytics.com/mp/collect?measurement_id=G-XXXXXXX&api_secret=SECRET Content-Type: application/json { "client_id": "1234567890.0987654321", "events": [ { "name": "purchase_confirmed", "params": { "value": 850, "currency": "USD", "transaction_id": "ORD-4471" } } ] }Step 6
Handle consent state before any tag fires
Google's consent mode requires setting default consent state before the GA4 tag loads, then updating it once a user responds to your consent banner. Getting the order wrong either fires tags before consent is known or blocks all measurement including consent-safe modeled conversions.
Consent mode default and update gtag("consent", "default", { ad_storage: "denied", analytics_storage: "denied", wait_for_update: 500 }); // after the user accepts in your consent banner gtag("consent", "update", { ad_storage: "granted", analytics_storage: "granted" });Step 7
Reconcile ad platform numbers against GA4 without expecting a match
Google Ads, Meta, and GA4 each use different attribution windows, different identity resolution, and different definitions of a conversion. A campaign showing 40 conversions in Google Ads and 30 in GA4 is not automatically a tracking bug. Check attribution window length and click-through versus view-through settings before assuming something is broken.
Document the expected variance range for each platform pairing once, based on their attribution settings, so the next person does not re-investigate a known and acceptable gap every month.
Step 8
Verify with a real test conversion, not the debug view alone
GA4's DebugView confirms events fire and parameters are correct, but it does not confirm the event graduated to a key event or that server side deduplication actually worked. Submit one real test conversion through the full funnel and confirm it appears once, correctly attributed, within 24 to 48 hours.
Pitfalls
What goes wrong in practice
- Marking every dataLayer event as a GA4 key event, which makes the conversions report meaningless within a few weeks.
- Firing the GA4 tag before consent mode's default state is set, which can cause tags to run unconditionally in some Tag Manager configurations.
- Sending server side and client side events for the same action with no shared ID, producing a silent doubling of reported conversions.
- Expecting Google Ads, Meta, and GA4 to report identical numbers. They measure different things by design and will not converge.
- Never revisiting the event taxonomy after a redesign changes form structures or button markup, leaving triggers silently broken.
Why do my Google Ads conversions never match GA4?
They use different attribution models. Google Ads often counts view-through conversions and uses its own click attribution window, while GA4 uses its own model based on the attribution settings in the property. A gap is expected. The goal is a documented, stable gap, not an identical number across platforms.
- Write the event taxonomy before implementing any tags
- Deduplicate client and server events with a shared event ID
- Set consent defaults before any tag fires, then update on user response
Related
Where this fits
Rather have this done for you
We run these steps as part of a program, with the checks automated where they can be.