One of your two numbers has a decimal in it and the other one never will. Google Ads reports fractional credit for conversion events, your practice management system reports whole rows for people, and the difference is either a capture bug you can fix this week or an arithmetic identity nobody wrote down. One test separates those cases, and it is not a conversion setting: split the share of CRM records carrying a click id by entry path, then see whether the paths disagree with each other or only the devices do.
The tables below are filled-in examples, not a report on anybody's account. Figures are synthetic, proportions are typical, and the shape is the part worth reading.
Quick answer: split click id capture by entry path before you audit anything else
I check whether the click id landed on the record before I look at a single conversion setting. The check shrinks the gap by nothing. It decides whether the gap can be settled at all: a record with no click id is unattributable forever, and auditing count settings above it is work on a ledger you cannot join.
Split the capture rate by entry path and by device. Never read it blended.
| Entry path | Device | Records created | Records carrying a click id | Capture rate |
|---|---|---|---|---|
| /request-appointment form | Desktop | 84 | 79 | 94.0% |
| /request-appointment form | Mobile | 141 | 132 | 93.6% |
| /new-patient-offer landing form | Mobile | 66 | 61 | 92.4% |
| Booking widget (vendor subdomain, iframe) | Desktop | 52 | 3 | 5.8% |
| Booking widget (vendor subdomain, iframe) | Mobile | 118 | 2 | 1.7% |
| "Book" link on the Google Business Profile | Mobile | 37 | 0 | 0.0% |
| All paths | 498 | 277 | 55.6% |
Synthetic figures, proportions typical.
The blended 55.6 percent is what keeps this bug alive for a year: it reads as ordinary consent and browser signal loss. The finding is two populations, one near 93 percent and one near 2 percent, splitting along the entry path rather than the device. Consent loss moves with the device. A dropped click id moves with the entry path.
| What your split looks like | What you are looking at | What changes it |
|---|---|---|
| Entry paths more than 25 points apart | A capture bug on one path | A form or widget change, this week |
| Entry paths within 10 points of each other, iOS Safari 15 to 30 points under Android Chrome | Ordinary signal loss | Nothing you can edit on a form |
| Every path above 90 percent, on cells of 30 records or more | Capture is fine | Go audit the conversion actions instead |
Those thresholds are mine, not a published standard: device-level loss alone does not open a 25-point gap between two paths carrying the same tag.
Two rows sit near zero and only one is a bug. The Google Business Profile row should be zero: those clicks are organic and never carried a click id. The widget rows should look like the form rows and do not, because the booking step sits on a vendor subdomain inside an iframe, so the page holding the hidden field never saw the URL parameter and cannot read the first-party _gcl_aw cookie across that origin boundary. The six ways gclid capture fails silently covers the mechanics.
One trap, and it is why this survives a year in plain sight. The standard advice on a stubborn gap is that a small documented difference is normal, so a stable ratio gets filed as documented. A dropped click id produces a perfectly stable ratio, because it has been broken continuously since the redesign that caused it. Stability tells you the failure is constant, not that it is absent.
Here is the pivot. The 30-record floor sits in the having clause rather than in the advice, and gbraid and wbraid are coalesced because iOS app and privacy-restricted clicks carry no gclid.
select
entry_path,
device_class,
count(*) as records,
countif(coalesce(gclid, gbraid, wbraid) is not null) as with_click_id,
round(100 * countif(coalesce(gclid, gbraid, wbraid) is not null) / count(*), 1) as capture_pct
from analytics.lead_records
where created_at >= '2026-09-01'
and created_at < '2026-10-01'
group by entry_path, device_class
having count(*) >= 30
order by capture_pct asc;No warehouse, and no reason to buy one for a question this small: export the month's leads to CSV, add a column that is TRUE when the click id field is non-empty and one for the entry path, then pivot. No click id field at all means the check is already answered. Either way, write your blended rate and your worst entry path at the top of the sheet: it is the confidence interval on everything below.
Why are your CRM and your ad account answering two different questions?
You are holding two ledgers that share no key: the ad account records clicks and the events it believes those clicks caused, the CRM records people somebody decided were worth a row.
Start with the decimal, because it settles the argument fastest. Under data-driven attribution one booking is split across the clicks that led to it, so the Conversions column reports credit rather than headcount and lands on a fraction. A count of people cannot be fractional: the two columns are different nouns before they are different numbers.
Second, Google dates a conversion to the click and not to the form fill, so a September conversion can belong to an August click. That alone makes month against month a rough comparison, which is why marketing analytics that ties ad spend to revenue starts at the join rather than at the dashboard.
If your two disagreeing numbers are Google Ads and GA4 rather than Google Ads and your CRM, stop here: those two disagree about which events each could physically witness, a different question with a different method, and I work it in why Google Ads and GA4 conversions never match. Everything below assumes both systems hold a record of a person.
The capture test removed nothing from either number. The four checks below do, one named line at a time. Wait seven days after the month closes before you pull them, because Google restates recent days for about a week, and count the new CRM records sourced to paid search over the same window, so both sides of the sheet cover the same days.
Check 1: is one submission firing two conversion actions?
Now the arithmetic starts. Pull every conversion action for the month with its count and its Count setting, then find two that can fire on one event: a form submit and a thank-you page view, a button click and the confirmation the server returned. Both are real, both are counted, one person caused them.
Two settings decide the damage. A Count setting of Every turns one conversation into several conversions, and the Conversions column carries only actions marked primary, so an action you consider important can sit in All conversions and be missing from the number you are comparing. For that split, what the Conversions column is actually made of is the shorter read.
Segment by conversion action rather than trusting the blended total, because duplicates cluster on one landing path and therefore inside one campaign. On the sheet: minus 12.0.
Check 2: is the same person in the ledger twice under two identities?
Someone fills in the form on Tuesday, does not hear back fast enough, and calls on Wednesday. Two ad-driven events happened, so the ad account is right to hold two conversions, while your CRM has one contact because a human recognized the phone number. Declare whether your sheet reconciles events or people before this line gets a number, and match on a hashed phone number rather than a name, since names are typed by whoever picked up. If calls are a real share of your lead flow, watch for double counting between a native CallRail import and your own conversion action, the same duplicate in a different hat.
Then the counterexample nobody warns you about, definitional rather than broken. Many systems deduplicate an inbound lead into an existing contact when the phone or email already exists, so an established patient booking again creates no row. The ad account counted a real conversion, and the CRM is right by its own definition. Label that line definitional or you will chase it for a week. On the sheet: repeat callers on a Count setting of Every, minus 7.0, and existing patients rebooking, minus 3.0.
Check 3: does your conversion window match how far ahead patients book?
The default click-through conversion window on a web conversion action is 30 days, configurable from 1 to 90. A booking made 40 days after the click exists in your schedule and not in your ad account, and no upload rescues it, because the Data Manager API upload path will not accept a conversion whose click fell outside the window. That is the one line of the sheet running the other way, adding to the CRM side rather than subtracting from the ad side.
Lead time does the rest. If median booking lead time exceeds one third of the reporting period, about 10 days on a monthly report, reconcile by booking cohort instead of calendar month. Otherwise the two numbers are wrong in opposite directions inside one table: September's conversions include August clicks, and September's records include October appointments counted nowhere you are looking.
Three lines for the sheet: net shift from dating conversions to the click, minus 3.4. Bookings made for October appointments or later, minus 6.0. Ad-influenced bookings after the click window closed, plus 4.0.
Check 4: what never reached the CRM in the first place?
Two things live here. The first is junk: bots, tests, a wrong number typed twice, form fills nobody opened a record for. Count them, subtract them, move on. Minus 4.0.
The second is why I never take a conversion count as evidence that something happened. A booking widget showed the patient a confirmation screen without reading the API response: success on a non-2xx, and on a 200 whose body said the slot was taken. The tag fired on that screen, so the ad account counted screens shown while the schedule counted appointments created, with no error raised in either. The front desk was happy. There was no row. I traced that in a booking widget that reported success without reading the API response.
What found it was a day-level comparison, and that is the part nobody publishes. I put the conversion count for that action by day next to appointments created by day. The ad line held flat all week while the schedule had holes, and the holes fell in the same two afternoon slots every day. A flat line beside a punctured one is the signature, and no ratio-based monitor raises it, because a widget that confirms without checking is broken consistently, not intermittently. If your version is a conversion action pointed at a button instead of a booking, the ad account is counting intent rather than patients, the same defect one layer up. Minus 1.0 on the sheet.
Assemble the sheet: eight named lines between two numbers
Every line now has a number. Here is a filled-in example of the document you have been building, top line from the ad account, bottom line from the schedule. The figures are synthetic, the proportions typical, and the column that earns its place is the last one.
| Sheet line | Effect | Count after | Who fixes it |
|---|---|---|---|
| Google Ads Conversions column, September | 63.4 | ||
| One submission firing two conversion actions | -12.0 | 51.4 | Whoever owns the ad account |
| Repeat callers on a Count setting of Every | -7.0 | 44.4 | Whoever owns the ad account |
| Net shift from dating conversions to the click | -3.4 | 41.0 | Nobody. It is how Google files |
| Junk submissions no one opened a record for | -4.0 | 37.0 | The form, plus a spam filter |
| Bookings made for October appointments or later | -6.0 | 31.0 | Nobody. Report by cohort instead |
| Existing patients rebooking, so no new record | -3.0 | 28.0 | Nobody. It is a definition |
| Widget confirmed, the schedule never created it | -1.0 | 27.0 | The booking vendor, today |
| Ad-influenced bookings after the click window closed | +4.0 | 31.0 | The conversion window setting |
| New patient records created, sourced to paid search | 31 |
Three of those eight lines have nobody against them, and that is the useful part: a line named as definitional stops being reopened every quarter, which leaves five somebody can go and change.
What is this gap actually costing you this month?
| Campaign | Spend | Ads conversions | Reported CPA | Real records | True cost per record |
|---|---|---|---|---|---|
| Implant Consults, Exact | $9,400 | 21.0 | $448 | 19 | $495 |
| New Patient Offer, Broad | $9,500 | 42.4 | $224 | 12 | $792 |
| Account | $18,900 | 63.4 | $298 | 31 | $610 |
Synthetic figures, proportions typical.
Blended, that account reports $298 per conversion and pays $610 per new patient record, a 2.05x error, and the headline number is the least interesting part. The duplicate conversion action sits on one landing path and therefore inside one campaign: reported, the broad campaign looks half the price of the exact one, and in reality it costs about 60 percent more per patient. Every budget conversation there ran backwards.
The account does not merely display the wrong number, it spends toward it. A target CPA bid strategy reads the same inflated column you do and reallocates faster than a person would, which is why a stable, documented ratio is not the reassurance it looks like: it is compatible with an inverted campaign ranking underneath it. The fix is not a better dashboard, it is to count the patient who showed up, not the form submit.
Close it once instead of explaining it every month
Under about 20 ad-sourced leads a month this is a spreadsheet you fill in once a quarter, not a build, and I will say so rather than quote you for one. Above that, fix capture on the broken path, then send back the outcome your schedule confirms instead of the event your website guessed at, which is what patient conversion tracking buys.
The usual advice here is that a small documented gap is normal: agree on a ratio, watch it, move on. I publish no normal band, deliberately, because a band tells you to accept a difference without knowing what is inside it. A ratio is a way of not knowing something, and this one has your budget stapled to it: the campaign that looked half the price above was quietly costing the most per patient. So the finish is not a ratio, it is a list you can read out without opening a second dashboard. Ads counted 63.4, the schedule created 31 records, and here are the eight lines in between, each with a number, a name, and either an owner or a reason nobody owns it. Three will never be fixed, and naming them is what stops them being reopened in January.
Tags
Frequently asked questions
Why does Google Ads report more leads than my CRM?
Because the two systems count different things and neither is lying. Google Ads counts credited conversion events, including two actions firing on one submission, repeat callers on a Count setting of Every, and junk form fills, while your CRM counts records a human decided to create. Work down a reconciliation sheet line by line and the difference usually resolves into six or seven named items.
How do I check whether the click id actually reached my CRM record?
Export one month of records and split the capture rate by entry path and by device, not as a single blended number. A blended rate around 55 percent looks like ordinary signal loss, but if one entry path is above 90 percent and another is near zero in the same month, that is a dropped click id and not a privacy effect. Ignore any cell with fewer than 30 records.
If someone fills in a form and then calls, is that one lead or two conversions?
One person, two conversions, and your ad account is right to count both. That is only a problem when you compare the total to a CRM that deduplicated them into a single record, so decide up front whether your sheet reconciles to events or to people and hold that definition for every line.
Can I recover leads whose click id was never stored?
Not at click level, no: with no click id there is nothing to tie that record to a specific ad click, and no upload will invent one. There is one identity-based path, matching on hashed contact details you already hold, and for a medical practice I usually decline to take it, because it means deriving identifiers from patient records and sending them to an ad platform. Fix the capture, reconcile the past on counts, and treat those months as unattributable.
How big a gap between Google Ads and my CRM is normal?
I publish no normal band for it, deliberately. A band tells you to accept a difference without knowing what is inside it, and the same 2x gap can be a duplicate conversion action on one account and a dead click id field on another. The gap is acceptable when every line between the two numbers carries a number and a name, whatever the total happens to be.
Can I close this gap without replacing my CRM or booking system?
Usually yes. Most of these gaps come from where the click id is captured and how conversion actions are configured, both of which sit outside the CRM, and the fix is a capture module plus one upload job rather than a migration. Replacing a booking system is warranted only when it will not carry a custom field through to the record at all.
Need something like this built?
Free 15-min discovery call. I'll listen, ask honest questions, and tell you if I can help.