All posts
Measurement

Google Ads vs GA4 Conversions Don't Match: Offline Leads

Google Ads vs GA4 conversions don't match because each counts a different set of events. Subtract in order, starting with what GA4 could never see.

September 6, 2026·17 min read·by Olexander Cheberko
Table of contentstap to expand

128 and 91.

Same four weeks, same account, and both numbers are right. Subtract, in a fixed order, every conversion GA4 could not physically have witnessed. What survives those lines is the only part worth an afternoon, and whether it holds its ratio week to week tells you more than how big it is.

Every figure below is synthetic and illustrative. I publish no client data, so the numbers are invented to make the failure modes visible. The method is real.

Quick answer: subtract in order, then read what is left

  1. Gate first, subtract second. Ask which system could physically have seen each conversion. Anything that never happened in a browser session on your site is not a GA4 shortfall, it is a row GA4 never had.
  2. Reconcile on click date, and keep conv. time open beside it. Spend and offline uploads are both keyed to the click, so the ledger only balances on the click date. Add "Conversions (by conv. time)" next to the default column as an instrument, not as the number you reconcile on.
  3. Disqualify the recent weeks. A week that closed a few days ago is still growing in Google Ads, and reading it gives you a confident wrong answer.
  4. Work a ledger, not a checklist. Start from the Google Ads count and net off one category per line, carrying the balance down.
  5. Read the residue. Its size decides whether anything is broken. Its stability decides whether you should care.

Before subtracting anything, know what the Conversions column is actually made of in your own account, because primary and secondary actions decide which conversions are in that headline number at all.

Which of these two systems could physically have seen this conversion?

That the two systems are not supposed to match is the easy half, and nearly every page on this query says it. The half that pays is knowing which rows the difference is made of, and taking them off in an order that leaves something readable at the bottom.

For a practice this is a category error before it is a discrepancy. The booking happened in an EHR or a booking system, often by phone, often days after the click, and no attribution setting will ever produce a GA4 event for something that never touched a browser.

GA4 counts key events that fired in a browser session on your site. Google Ads counts conversions it can attribute to a click, wherever that conversion was recorded. Two different populations, and for a business that books by phone they are not close. Take one booking and walk it down.

One booking
which system could have seen it at all?
Did it happen in a browser session on your site?
no: phone, walk-in, callback
Analytics never saw it
arrives only as an upload
yes
Did the visitor allow analytics storage?
no
Modelled, not counted
no analytics row exists
yes
Did it finish in the same session as the ad click?
no: days later, or another device
Credited twice, differently
click versus later session
yes
Both systems saw it
this row should reconcile
Walk one booking down this tree and you find out which system could ever have seen it. Three of the four endings are structural, which is why chasing them in the tag manager never closes the gap.

Three of those four end states are rows GA4 was never going to hold. A patient who calls the front desk and books for next Tuesday leaves no session, no page view, no key event. That booking reaches Google Ads only if you put it there, which is what an offline conversion tracking setup does: capture the click id at booking, upload the outcome later, filed against the click date.

The middle branch is the one people misread. When click and booking are days apart, or on different devices, Google Ads credits the click while GA4 credits the session it resolved for itself. The credit landed in a non-paid row, and where it landed is a different article, though the symptom, reports that argue with each other, is the same.

For a US medical practice, part of this gap is deliberate

GA4 is not HIPAA compliant and Google offers no BAA covering it, so a booking flow built for a practice keeps appointment context out of GA4 on purpose: no procedure name, no location detail, no identifiers on the key event, and in several builds no confirmation-page event at all, because the confirmation URL itself carries context. That widens the gap by design, so a practice reading the raw gap as a tracking defect is reading its own privacy posture as a bug. The boundary that makes the offline path workable anyway is the one I state everywhere: only an anonymous click id, a value, and a timestamp ever leave the system. HIPAA-conscious, and not legal advice.

If the gate nets zero, this ledger is the wrong tool

If your conversion is an on-site purchase, you upload nothing and you run no call conversions, the gate nets zero. Skip to the modeling and attribution lines, where the honest expected gap is small.

If GA4 is higher than Google Ads, the witness test is not your problem either. Check that you filtered GA4 to paid traffic, then whether your Ads action counts one conversion per click while your GA4 event fires on every submission. That direction has a different diagnosis, and a shorter one.

Which clock do you reconcile on, click date or event date?

There is no single clock here, and picking one is the decision this whole comparison turns on.

Google Ads files a conversion against the date of the click, so an offline conversion uploaded on August 19 for a click on August 3 lands in the week of August 3. That is why a week you already closed can grow. GA4 does the opposite. Key events are counted on the date the event fired, session-scoped, credited to the session GA4 resolved for itself. Ad click Tuesday, booking Friday from an organic visit: one conversion in Google Ads, one non-paid key event in GA4, no row in common.

The obvious move is to switch Google Ads to "Conversions (by conv. time)", which re-files conversions on the day they happened and puts both systems on something that looks like the same calendar. I do not reconcile that way. Spend is reported on the click date, the click id you upload against belongs to the click date, and every offline row you are about to subtract was filed on the click date. Reconcile on conv. time and you divide one week's conversions by another week's spend, and the ledger stops matching the upload it exists to audit.

So reconcile on click date, and keep the conv. time column open beside it as an instrument rather than as an answer. Read together over a long enough range, the pair tells you the shape of your own lag: click date says which week paid for the conversion, conv. time says when it actually happened, and the distance between them is the delay you have to wait out before a week is readable at all.

Two more mechanics decide this comparison and almost nobody names them. Google Ads reports modeled conversions inside the same column as observed ones, with no toggle to separate them, so part of the Ads number has no underlying row anywhere. And an upload is accepted only while the click sits inside the conversion action's click-through window, which caps how late a booking can be filed. Both behaviors belong to Google and both have changed before, so confirm them against your own account's column definitions before you build a rule on either.

Why does last week keep growing after you already reported it?

The four weeks behind those two numbers, week by week. Google Ads read by click date, GA4 key events filtered to Google Ads traffic, both pulled on August 19.

Week (Mon to Sun)Google Ads conversionsGA4 key eventsGapWhat the rule says
Jul 20 to 263624+12 (+50%)Settled. Offline share plus modeling. Reconcilable.
Jul 27 to Aug 23322+11 (+50%)Settled. Same shape as the week before. Reconcilable.
Aug 3 to 93423+11 (+48%)Just settled: ten days closed against a ten day settle time. Third flat week, the signal that matters.
Aug 10 to 162522+3 (+14%)Closed three days ago. Uploads are still landing, so the week is unfinished, not fixed. Do not read it.
Four-week total12891+37 (+41%)The number in the first line, and the one you were about to defend on Thursday.

Synthetic figures, proportions typical.

Week four is the trap. It looks like the gap finally closed, and what happened is that the week was pulled three days after it ended with uploads still arriving. A settled week four would have read about 33.

Measure your own settle time instead of guessing at it

  1. Pick one closed click week.
  2. Add "Conversions (by conv. time)" next to the default column and segment by conversion action.
  3. Snapshot that week's click-date number on a schedule and diff it, until it stops moving.

Illustrative run for the July 27 to August 2 week: pulled Aug 5 = 26, Aug 12 = 33, Aug 19 = 33, Aug 26 = 33. It had stopped moving by the ten day snapshot, so ten days is that account's settle time, not the number in anyone's blog post. Take the conservative end of your own grid, because you only know it stopped somewhere between two pulls. Four snapshots, five minutes each, and you never argue about a fresh week again.

I have run these uploads two ways, out of a warehouse and out of a flat scheduled job, and the schedule shapes the report more than the tracking does. The flat job batched on Mondays, so every Friday click week looked well behaved right up until Monday afternoon rewrote it. That is not a tracking failure. That is my own cron job showing up in someone's board deck, and it is why step one of my ledger is a calendar question rather than a settings question.

Work the gap as a running balance, not as a checklist

First, get a GA4 number that does not move when somebody edits a report filter. That means the BigQuery export, with placeholder project and dataset ids.

SELECT
  DATE_TRUNC(PARSE_DATE('%Y%m%d', event_date), WEEK(MONDAY)) AS wk,
  COUNT(*) AS ga4_key_events,
  COUNTIF(privacy_info.analytics_storage = 'No') AS consent_denied_rows
FROM `myproject.analytics_401234567.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260720' AND '20260816'
  AND event_name = 'booking_confirmed'
  AND collected_traffic_source.gclid IS NOT NULL
GROUP BY wk
ORDER BY wk;

It filters on collected_traffic_source.gclid rather than on source and medium strings, because the click id is the key the upload is built on, so both sides of the ledger are keyed the same way. And consent_denied_rows is not decoration: I have a live export carrying consent-denied rows, and if that column is a meaningful share of the total, part of your GA4 shortfall is consent rather than tracking, and no tag work recovers it.

Now take the three settled weeks only, 103 against 69, and carry the balance down.

LineWhat comes offAds-comparable countResidue vs GA4 (69)
StartGoogle Ads, settled weeks103+34 (+49%)
1Uploaded offline conversions, bookings taken by phone or callback88+19 (+28%)
2Calls placed from the ad itself, never a browser session75+6 (+9%)
3Modeled and cross-device conversions GA4 does not report71+2 (+3%)

Synthetic figures, proportions typical. Line 3 is the last one you can account for, so the two rows left over are the residue, and the residue is the only part worth an afternoon. Everything above it is structural and will still be there next month.

Line 1 is the biggest and the most misdiagnosed: 15 bookings that never touched the website and exist in Google Ads only because somebody put them there, over the Data Manager API upload path. If a web conversion action and an uploaded offline conversion both fire for the same booking, this row double counts on the Ads side, which is the trap described in the full loop from click to arrival.

Line 2 is simpler: a call placed from the ad has no browser session behind it, so GA4 can never hold it, and 13 in three weeks is unremarkable for a practice running call assets on brand terms. Line 3 you cannot audit row by row, because Google does not expose modeled conversions separately, so you subtract a category and accept an estimate.

Read the residue: is it flat, or is it moving?

Two against 69 is 2.9 percent, inside my tolerance. I would leave it alone and watch whether it stays there, because the shape matters more than the size.

The residue is not supposed to reach zero, and both reasons are structural. The two sides of the ledger are still on different clocks, click date on one and event date on the other, so a booking that lands several days after its click drifts across a week boundary on one side and not the other. And line 3 is a category estimate rather than a row count, so it carries its own error into the bottom line.

A residue holding its ratio across three settled weeks is a measurement system behaving, even at 6 or 8 percent. A residue whose ratio moves several points week over week is the pattern I check first when I suspect real breakage, and it is not proof on its own, because a shift in the phone-to-web booking mix moves it too. That is the distinction worth learning to see: a mix change moves line 1 and leaves the residue where it was, while breakage moves the residue itself. Reading both lines weekly on a schedule, rather than at quarter end when the argument has already started, is most of what closed-loop attribution that ties ad spend to revenue is actually for.

A negative residue is louder. If subtracting your uploads takes the Ads-comparable count below the GA4 count, stop subtracting and go find the double count.

Whether a fall inside one of these systems is a real drop or ordinary variance is a statistical question this article does not answer. A ledger compares two sources at one moment; movement over time needs a different method.

Four ways I have gotten this wrong

  1. Comparing an unfiltered GA4 total against a Google Ads conversion total. A whole-site number against a paid-only number, then a confident verdict that tracking is broken.
  2. Reconciling on Monday because the report is due Thursday. That reads an unsettled week and gets the verdict wrong in the friendly direction, which is the dangerous one.
  3. A booking-confirmation key event on a page that survives a refresh. GA4 climbs on repeat views while Google Ads, deduping against the click, does not.
  4. Running the practice's own web conversion action and an uploaded offline conversion for the same booking. That double counts on the Ads side only, and it is what a negative residue almost always means.

What gap do I agree to before I take the engagement?

Three numbers, committed before the work starts, each with the reason attached.

The reconcilable window is p90 booking lag plus upload lag. Illustrative: a 7 day p90 booking lag plus a 3 day upload lag means I do not reconcile a week that closed less than 10 days ago, which is what the snapshots above returned for the same account. The arithmetic and the snapshots do not always agree. On the flat scheduled job the batch interval stacks on top of the booking lag, so the arithmetic understates the wait and only the snapshots catch it. When the two disagree, take the larger. The theoretical bound is worse still, since a click can convert anywhere inside the click-through window, so a 90 day window leaves a click week technically unsettled for 90 days plus upload lag. Choose that window deliberately: it sets how long your reporting stays soft.

The tolerance is 10 percent of the GA4 count on settled weeks. I picked 10 because below that line the residue is smaller than the ordinary week-to-week swing in the practice's own booking volume, so the ledger can no longer separate a tracking fault from a quiet week. The number belongs to the account rather than to me: if your volume is steady enough that the weekly swing is tighter than 10 percent, tighten the tolerance with it.

The residue ratio should not move more than about 5 points week over week across three settled weeks. If it does, something underneath the ledger changed.

Which number should move tomorrow's bid?

Only one of the three can. Take $9,400 of Google Ads spend across those four weeks and three candidate denominators.

DenominatorCountCost perWhat it really is
Google Ads Conversions column128$73.44The bidder's view, modeled conversions included
GA4 key events, paid traffic91$103.30The on-site bookings GA4 was able to witness
Booking system, from a Google Ads click117$80.3498 booked on the site, of which GA4 recorded 91, plus 19 by phone or callback

Synthetic figures, proportions typical.

The seven rows between 98 and 91 are the same category the ledger estimates on line 3, bookings that did complete on the site and never reached GA4 as key events, seen from the other side of the join and across four weeks rather than three. Close, and deliberately not equal: line 3 removes a modeled estimate from the ad platform's count, while these seven are rows the booking system actually holds. Two ways of sizing the same leak, which is why they land near each other without reconciling to zero. The three counts also sit on three different clocks, which is tolerable across four settled weeks and useless across one fresh one. GA4 understates the bookings actually produced by 22.2 percent and Google Ads overstates them by 9.4 percent. At that budget, the difference between $103 and $80 is the difference between a meeting that cuts spend and a meeting that adds it.

There is a third count in this building, the one your booking system keeps, and it is the only one in the room that is neither modeled nor sampled. It held all 117 on August 17, and Google Ads did not hold its version of them until the end of the month. Every reconciliation guide I have read ends by putting that count in the board deck. I would rather put it in the auction. A number that stops at a slide changes nothing about what Google buys tomorrow, while the same number uploaded against the click that produced it changes the next bid, which is the entire reason I build marketing analytics that ties ad spend to revenue rather than prettier reports. If the number in your deck and the number in your upload are different numbers, one of them is decoration. Reconcile if you enjoy the exercise. Upload if you want the spend to move.

Tags

google ads vs ga4 conversionsconversion reconciliationga4 key eventsoffline conversionsattribution

Frequently asked questions

What is a normal gap between Google Ads conversions and GA4 key events?

After you subtract the conversions GA4 could not witness, I expect what is left to sit within about 10 percent of the GA4 count on weeks that have finished settling. The raw gap before that subtraction is not a diagnostic at all, because a business that takes most of its bookings by phone can run far apart from GA4 and be perfectly healthy. Whether the leftover holds steady week to week matters more than how big it is.

Why are my Google Ads conversions higher than my GA4 key events?

Usually because Google Ads is counting things GA4 never had a chance to record: offline conversions uploaded from your booking system, calls placed straight from the ad, and modeled conversions. GA4 only counts events that fired in a browser session on your site. Filter GA4 to Google Ads traffic first, then subtract those three categories before you conclude anything is broken.

Why did the gap between Google Ads and GA4 suddenly shrink last week?

Most often because you pulled the week too early. Offline conversions are filed against the date of the click rather than the date you uploaded them, so a recent week keeps growing in Google Ads for days after it closes. A week that looks unusually well matched is frequently just unfinished.

Should I import GA4 key events into Google Ads instead of uploading offline conversions?

Not if the booking happens off the website. Importing GA4 key events copies GA4's blind spots into your bidding, because anything GA4 never saw stays invisible to the bidder. Import is a reasonable shortcut only when the whole conversion completes on the page and you want one fewer tag to maintain.

Does switching the attribution model make Google Ads and GA4 agree?

No, and it can widen the gap. Attribution decides how credit is divided among touchpoints inside one system. It does not create an event in GA4 that never happened, and it does not remove an uploaded conversion from Google Ads, so change it for a reason of its own and not to close a reconciliation gap.

How do I tell a real tracking break from a change in my phone-to-web booking mix?

Read the residue, not the raw gap. A shift in booking mix moves the uploaded line of the ledger and leaves the residue ratio roughly where it was, while a tracking break moves the residue itself. So if the uploaded share climbed and the residue held its usual ratio across three settled weeks, your mix changed and your tracking is fine.

Need something like this built?

Free 15-min discovery call. I'll listen, ask honest questions, and tell you if I can help.

More in Measurement