Tracking & Attribution

How Do You Track Booked Appointments to Google and Meta Ads?

Booked appointments break browser tracking. Capture click IDs at first touch, persist to the CRM, fire server-side to Google and Meta — counted once.

Quick answer

Track booked appointments by capturing every click ID (gclid, gbraid, wbraid, fbclid) at the first pageview, persisting them through every funnel step to the CRM contact record, and firing the booking as a server-side conversion: a click-ID-keyed offline import to Google Ads and a deduplicated CAPI event to Meta. Appointment funnels break browser tracking because the conversion happens on a calendar — often on a different page, subdomain, or day than the ad click.

If your setup looks like the typical broken one — an offline import nobody can account for, a stray tag from an old account, gbraid/wbraid not captured at all, a GoHighLevel workflow firing Meta CAPI and the Google import with no confirmed deduplication — this article maps each failure to its fix, covers the healthcare-compliance question with straight answers, and shows how PartialLeads ships the corrected pipeline.


Why do appointment funnels break conversion tracking?

Five structural reasons, each common enough that most clinics and consultation businesses have several at once:

1. The conversion is a calendar entry, not a page event. Pixels and tags fire on page loads and clicks. A booked appointment is a CRM state — it becomes real when the calendar slot is written, which may happen on a booking widget, after a redirect, or via a confirmation flow the tags never see. Optimizing Google Ads on “form fill” when the business only cares about “booked” trains Maximize Conversions on the wrong event — you buy form-fillers, not patients.

2. Multi-step funnels drop attribution between steps. The click ID arrives on step one’s URL. By the calendar page — three steps and possibly a subdomain later — it’s gone unless something deliberately carried it. Cross-domain moves (main site → funnel subdomain → booking page) reset first-party context each hop; every untagged page in the chain is a place the trail dies. The result: bookings that exist but attribute to nothing.

3. iOS click IDs aren’t the one everyone tests for. Google issues gclid on most clicks — but iOS traffic arrives with gbraid or wbraid instead, Apple-privacy-era variants that most hand-built captures ignore entirely. On an audience that’s three-quarters mobile, failing to capture gbraid/wbraid means a large share of real ad-driven bookings simply cannot be imported back to Google, no matter how good the rest of the pipeline is.

4. Workflow-fired events have no browser context and no dedup. CRM workflows (GoHighLevel’s included) can post conversions when a booking lands — but a workflow knows the contact record, not the session. No _fbp, no _fbc, no IP or user-agent: Meta receives a low-match event it may not attribute. And when the same workflow feeds both Meta and a Google import — with a browser pixel possibly firing too — nothing shares an event_id, so nothing deduplicates. Some bookings count twice, others zero.

5. Tag archaeology. Old accounts leave tags behind. A second Google Ads tag from a defunct account firing on live landing pages pollutes data at minimum; at worst it sends your conversions to someone else’s account. Stray-tag audits are boring, unbillable-feeling work — which is why nobody’s done one.

The compound effect on a $12K/month spend: Maximize Conversions bidding on corrupted signals, budget flowing to campaigns that produce form-fills instead of bookings, and no way to know your real cost per booked appointment — the only number that matters at $500 a booking.


What does the clean architecture look like?

One principle organizes everything: capture identifiers in the browser once, at first touch; fire conversions from the server, keyed to those identifiers; count each booking exactly once per platform.

Concretely:

  1. First-touch capture of the full click-ID set. On the first pageview of any entry page — main site, every funnel subdomain, the hand-coded one — capture gclid, gbraid, wbraid, fbclid (wrapped into _fbc correctly), _fbp, and UTMs. Persist them server-side against a durable visitor identity, not in page-scoped variables that die at the next step.
  2. Write identifiers to the CRM contact. When the lead identifies themselves (form step, booking form), the stored click IDs attach to the contact record. Now the booking — whenever it lands, even days later — carries its originating click.
  3. Google Ads path: click-ID-keyed offline import. When the appointment is booked, send a conversion row keyed on the stored gclid (or gbraid/wbraid for iOS clicks) with the booking value. Google attributes it to the exact campaign and keyword. Retire the unaccountable legacy import; one import, one conversion action, documented. Notably, a gclid-keyed conversion contains no customer information at all — relevant below.
  4. Meta path: one CAPI event with stored identifiers and a deterministic event_id. The booking fires server-side carrying _fbc/_fbp from storage; if a browser pixel also fires a booking event, both carry the same event_id so Meta deduplicates. One booking, one count.
  5. Enhanced conversions for leads: a decision, not a default. It improves Google’s matching by sending hashed email/phone — which, for a medical practice, is a compliance decision (next section), not a checkbox.
  6. Verification you can reproduce. A test booking with a known click ID appears exactly once in Google Ads within its processing window, attributed to the right campaign at the right value, and once in Meta’s Events Manager — checked against the CRM’s booking list weekly thereafter. This is buildable by a specialist who has done GHL-specific attribution work. The honest difficulty ranking: the click-ID persistence across steps and subdomains is the hard part, the offline import is fiddly but documented, and the tag cleanup is tedious but mechanical. The recurring risk is the same as every hand-built pipeline — it has to stay correct across funnel edits, new subdomains, and workflow changes.

What about patient privacy? (The section most guides skip)

For healthcare businesses, the honest version matters more than the optimistic one:

Hashing is not invisibility. Enhanced conversions and Meta CAPI match quality work by sending hashed email, phone, and name to Google and Meta. SHA-256 hashing protects the value in transit, but you are still transmitting customer contact data to an ad platform — and for a medical practice, a contact record tied to a consultation inquiry can itself be sensitive. US regulators have scrutinized exactly this pattern on healthcare websites. Whether hashed identifiers are permissible for your practice is a question for your counsel, not for a tracking vendor or a blog post.

The good news: the Google path can carry zero patient data. A gclid/gbraid/wbraid-keyed offline conversion import transmits the click ID, a timestamp, and a value — no name, no email, no phone. Google matches it to the click it already knows about. For a compliance-constrained practice, click-ID-keyed import without enhanced conversions is the conservative architecture: full campaign attribution, nothing patient-identifying leaving the building.

Meta’s trade-off is real and should be stated plainly. A CAPI event carrying only _fbc/_fbp and event data transmits no contact information but matches at reduced quality; adding hashed email/phone raises match quality and transmits hashed contact data. That’s the actual dial. Decide where it sits with counsel — and treat any vendor’s answer to “what exactly leaves our systems, to whom?” as a screening question. Ask for the parameter list in writing, ask about subprocessors, and require the NDA/BAA conversation before access — from anyone, PartialLeads included.


How does PartialLeads fix booked-appointment attribution?

The pipeline above is PartialLeads’ native shape — capture at first touch, persist identity, fire server-side, count once:

The full click-ID set is captured by default — including the iOS ones. The tag captures gclid, gbraid, wbraid, gad_source, fbclid, _fbp, and _fbc (reconstructing _fbc in Meta’s required format when the cookie is missing), plus UTMs, referrer, and landing page — on the first pageview, on every entry surface including hand-coded pages, with one script. The gbraid/wbraid gap that hand-built captures miss — decisive on a 75%-mobile audience — is covered out of the box.

Identifiers survive the funnel because they’re stored, not carried. Every identifier persists server-side against a durable visitor ID from the moment of arrival — multi-step funnels can’t drop what isn’t riding in the page. The visitor ID survives Safari’s cookie limits because the server sets it. Contact fields are captured as they’re typed on any step, so identity attaches early — even when the visitor stalls before the calendar. Across subdomains, the identity graph unifies the journey once any contact field is captured, joining the subdomain sessions into one person.

The booking flows in from GoHighLevel and fires once per platform. GHL is a proven PartialLeads source — the booking event arrives via the GHL integration, matches to the originating session through tiered identity (visitor ID → email → phone → IP), and dispatches with the $500 value attached: a gclid/gbraid/wbraid-keyed row into the Google Ads offline import path, and a CAPI event to Meta carrying the stored _fbc/_fbp. Every event’s ID is deterministic — derived from the booking record and backed by a send-ledger — so the workflow-double-fire problem is structurally gone: retries, races, and parallel paths land on the same ID and are blocked.

The leads write back to the CRM with attribution attached. Captured leads deliver to GoHighLevel in native webhook format with their source and click IDs — the “click IDs written to the CRM contact record” deliverable is the default behavior of the loop, not a custom build.

The plain-English diagram is a product surface. Each lead’s journey timeline reads like the one-pager the brief asks for: ad click with campaign, funnel steps, booking, value — visible per lead, with first-touch and last-touch attribution side by side and revenue per campaign in the Attribution report. Verification is the same walkthrough: a test booking with a known click ID, watched from capture through dispatch in the activity log, then confirmed in Google Ads and Meta’s Events Manager.

Honest boundaries, because this is healthcare: PartialLeads transmits hashed contact data to ad platforms when configured to — that’s how CAPI match quality works, and the compliance section above applies to it as to any vendor. The Google click-ID path needs no patient data; the Meta match-quality dial is a counsel decision. Cross-subdomain stitching unifies on captured identity — a visitor who never enters any contact field on either domain remains two anonymous sessions. And NDAs, BAAs, and subprocessor disclosure are conversations to have with any vendor before access — ask PartialLeads the same screening questions you’d ask a freelancer.

The failure-to-fix map, in one view:

Broken today PartialLeads mechanism Where you see it working
gbraid/wbraid not captured (75% mobile) Full click-ID set captured at first touch, incl. iOS variants Click IDs visible on the lead record; iOS bookings importable to Google
Click ID lost across funnel steps/subdomains Server-side persistence against a durable visitor ID + identity stitching Journey timeline: ad click → steps → booking, one person
Unaccountable offline import One click-ID-keyed conversion path with an activity log Each booking = one row, one conversion action, auditable
Workflow fires Meta + Google with no dedup Deterministic event_id from the booking record + send-ledger “Deduplicated” in Test Events; counts match the calendar
Bookings attribute to nothing Tiered identity matching joins booking to originating click Revenue per campaign at $500/booking in the Attribution report
Click IDs missing from CRM contacts GHL-format webhook delivery with attribution attached Contact records in GHL carrying source + click IDs

What stays on the specialist’s list regardless of tooling: removing the stray legacy tag, retiring the unaccountable import, and the cross-domain GA4 linking — cleanup is cleanup. What stops being a project is the pipeline itself. The free tier (50 leads) covers the definition-of-done test end to end: place the booking, follow the click ID through, confirm it lands once in each platform.


Sources

  1. Google Ads Help — About offline conversion imports: https://support.google.com/google-ads/answer/2998031
  2. Google Ads Help — About enhanced conversions for leads: https://support.google.com/google-ads/answer/11347292
  3. Google Ads Help — Google Click ID (gclid) and auto-tagging: https://support.google.com/google-ads/answer/3095550
  4. Meta for Developers — Conversions API documentation: https://developers.facebook.com/docs/marketing-api/conversions-api
  5. Meta for Developers — Deduplicate Pixel and Server events: https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events
  6. HHS — Use of Online Tracking Technologies by HIPAA Covered Entities: https://www.hhs.gov/hipaa/for-professionals/privacy/guidance/hipaa-online-tracking/index.html

Frequently asked questions

QWhat's the difference between gclid, gbraid, and wbraid — and why does mobile share matter?
All three are Google click IDs. gclid is the standard one. gbraid and wbraid are the privacy-era variants Google issues for iOS traffic where App Tracking Transparency limits apply — wbraid for web-to-web, gbraid for app-involved paths. A capture script that only looks for gclid silently loses the iOS share of ad clicks. On an audience that's 75% mobile with a large iPhone share, that can be the majority of real ad-driven bookings — unattributable no matter how good the rest of the setup is.
QWhy is firing conversions from a CRM workflow a problem?
A workflow knows the contact, not the browser session. Its events carry no _fbp, _fbc, IP, or user-agent, so Meta matches them poorly — and when the same workflow feeds multiple platforms alongside a browser pixel, nothing shares an event ID, so nothing deduplicates. Workflows are the right tool for CRM automation and the wrong origin for conversion events. The correct origin is a pipeline that stores the session's identifiers at first touch and attaches them at dispatch, with deterministic event IDs across every path.
QHow do I attribute a booking that happens days after the ad click?
Persistence, not cookies-at-conversion. The click ID is captured and stored server-side at the first pageview, attached to the contact identity as soon as any contact field is captured, and retrieved when the booking lands — whether that's an hour or a week later. Google's offline import accepts conversions up to 90 days after the click, keyed on the stored gclid/gbraid/wbraid. The join runs on stored identity, so the delay and any device changes in between stop mattering.
QHow do I verify each booking is counted exactly once?
Two layers. Per-event: place a test booking from an ad click (or a URL with a known click ID), then confirm one conversion in Google Ads attributed to the right campaign at the right value, and one event in Meta's Events Manager — with browser and server rows sharing an event_id and marked "Deduplicated" if both fire. Ongoing: reconcile weekly — bookings on the calendar vs. conversions reported per platform should match within a few percent. Persistent overcounts mean duplicate emitters; undercounts mean the click ID isn't surviving to the booking.
QWhat should I clean up before installing anything new?
Three things, in order. First, inventory every tag on every page — including funnel subdomains and hand-coded pages — and remove anything from old accounts or unknown owners; a stray conversion tag pollutes or exfiltrates your data. Second, identify and retire any conversion import you can't account for, so the new path is the only path. Third, list every page in the funnel chain and confirm each one carries the capture script — an untagged step is where click IDs go to die. Only then install the new pipeline, so you're never debugging two systems at once.
QDoes this architecture work for dentists, med spas, law firms, and other appointment businesses?
Yes — the pattern is identical for any business where the conversion is a booked slot rather than an online payment: capture click IDs at first touch, persist to the CRM contact, fire the booking server-side to each platform keyed on stored identifiers, deduplicate deterministically. The compliance section scales with sensitivity: legal and financial consultations carry analogous confidentiality considerations, while a salon booking carries few. The architecture is the same; the parameter decisions differ.

Find the qualified leads your forms are currently throwing away.

Install PartialLeads on one landing page, send traffic, and compare what your CRM captured against what PartialLeads recovered and qualified.