GA4 guide: making conversions match your CRM
Why GA4 lead counts never equal CRM lead counts, which gaps are fixable, and the reconciliation workflow that ends the weekly argument.
"GA4 says 412 leads. HubSpot says 371. Which is right?" Both. Neither. Here's how to think about it — and how to shrink the gap to something nobody argues about.
Why the numbers can never be identical
- Consent. Visitors who decline analytics cookies convert invisibly to GA4 but still hit your CRM. This gap alone is often 10–25% in the UK/EU.
- Ad blockers stop GA4 collection; the form still submits.
- Counting methods differ. GA4 counts conversion events; your CRM counts unique contacts. One person submitting twice is 2 vs 1.
- Spam and sales-rep entries enter the CRM without ever visiting the site.
- Timezone cutoffs. GA4 property timezone vs CRM timestamps shifts boundary conversions across days.
Accept a residual gap of 5–15%. The goal is a stable, explained gap, not zero.
The fixable part
1. Deduplicate at the event level. Fire generate_lead only on genuine first submissions (gate it in the data layer, not the tag).
2. Pass a shared ID. Generate a UUID on form submit, send it to GA4 as an event parameter and into a hidden CRM field. Now you can join the two systems row-by-row instead of comparing totals:
const leadId = crypto.randomUUID();
window.dataLayer.push({ event: "generate_lead", lead_id: leadId });
// same leadId goes into a hidden form field → CRM
3. Reconcile weekly, automatically. A scheduled query joins GA4 (via BigQuery export) to CRM leads on lead_id and classifies every unmatched record: consent-blocked, spam, offline entry, duplicate. Unexplained mismatches page a human.
The report that ends the argument
| Category | Count | % |
|---|---|---|
| Matched in both | 351 | 82% |
| CRM only — consent/blocker (expected) | 44 | 10% |
| CRM only — spam | 12 | 3% |
| CRM only — manual entry | 9 | 2% |
| GA4 only — duplicate events (fix) | 11 | 3% |
Once every lead lands in a named bucket, the "which number is right" meeting simply stops happening. That's the whole point.