Why Your Embedded Booking Form Shows Conversions as Direct Traffic (And the Fix)
Embedded booking forms show conversions as direct traffic because the booking happens inside an iframe on the scheduler's domain — a separate analytics session with no gclid, no UTM parameters, and no campaign context. Your GA4 property never sees the conversion happen on your page, so it can't credit the ad click that caused it. The fix is a scheduler that fires the conversion event on YOUR domain, inside YOUR visitor's session, the moment the booking confirms — which is exactly what Cartoply's embed does automatically for GA4, Google Tag Manager, and the Meta pixel.
How to track ad conversions from an embedded booking form
- Embed the booking page with the provider's script, not a raw iframe
- Confirm the embed forwards UTM parameters and click IDs into the booking
- Fire the confirmation event on your own domain, not the iframe's
- Mark generate_lead (or the custom event) as a Key Event in GA4
- Import the Key Event into Google Ads as a conversion
- Sync stored gclids to your CRM for offline conversion uploads
Why iframes break ad attribution
Browsers treat iframes as fully separate documents. When a visitor lands on your page from a Google Ads click, their session carries a gclid and the campaign's UTM parameters. The moment they interact with an embedded scheduler living on a different domain, a brand-new session starts — one that knows nothing about the ad click that brought them there.
GA4's session model can't cross that boundary without explicit cross-domain configuration on both properties. Most third-party schedulers won't give you access to their GA4 property, so that route is a dead end. The result: your campaigns look like they produce zero conversions, while "direct" traffic inexplicably books jobs all day. Your ROAS looks terrible. Your client asks why they're spending money on ads.
This isn't a GA4 bug. It's working exactly as designed. The problem is architectural — a scheduler that lives on its own domain can never natively share your session. The only real fix is a scheduler that fires the confirmation event on your page, not its own.
What firing the event on your own domain changes
When the conversion event fires in the host page's session, the ad click that opened that session gets full credit. No configuration tricks. No fragile cross-domain rules.
Cartoply's embed works by listening for the booking confirmation inside the iframe, then pushing events to the host page's own tracking stack: a cartoply_booking_confirmed event to the GTM dataLayer, a generate_lead standard event via GA4 gtag, and a Schedule event to the Meta pixel. All three fire in the visitor's existing session. The gclid is already there. Campaign and ad group context is already there. You just need to mark generate_lead as a Key Event in GA4 and import it into Google Ads — steps 4 and 5 in the list above.
For a deeper look at the exact events and dataLayer schema, see Cartoply's conversion tracking docs. If you're comparing schedulers on this specific capability, the gap between Cartoply and a standard iframe embed is covered in the Cartoply vs Calendly breakdown.
Closing the loop: attribution saved on the booking itself
Firing the GA4 event is the first half. The second half is what happens after the booking — when a booked estimate becomes a signed job three weeks later.
Cartoply stores utm_source, utm_medium, utm_campaign, utm_term, utm_content, gclid, fbclid, msclkid, the referrer, and the landing page URL on every booking record. That data travels with the booking in every Zapier payload. Your CRM receives it automatically. When the deal closes, you upload the gclid against the final sale value to Google Ads as an offline conversion — and the original ad click gets credit for the actual revenue, not just the lead.
This matters most for home service and field sales clients where the gap between "booked estimate" and "signed contract" is days or weeks. If you're already sending bookings to a spreadsheet or CRM via Zapier, the post on how to auto-send new bookings to Google Sheets via Zapier shows exactly how that payload arrives and what fields to map.
Tips for agencies managing multiple client sites
Each client site is its own host page — which is exactly how this should work. Cartoply's embed fires into whichever GA4 measurement ID and GTM container are present on the host. No shared property. No cross-contamination.
A few things to confirm for each client setup:
- GTM is published on the client's site before you test — unpublished containers don't fire.
- The booking link is embedded via Cartoply's script snippet, not copy-pasted as a raw
<iframe>tag. Raw iframes don't get the event bridge. See the embed a booking page docs for the correct snippet. - UTM parameters survive navigation — Cartoply captures first-touch UTMs, so even if a visitor browses around before booking, the original campaign gets credit.
- Test in GA4 DebugView before going live. Trigger a test booking and confirm
generate_leadappears with the correct campaign source in the debug stream.
For a full walkthrough of how Cartoply is built for agency workflows — white-label booking links, multi-client management, and client reporting — see Cartoply for agencies. And if your clients are comparing scheduling tools themselves, the post on what free scheduling software actually gets you gives a straight answer on where attribution support tends to disappear.
Frequently asked questions
Why do my booking conversions show as direct in GA4?
Your booking form is running inside an iframe on a different domain, which creates a separate analytics session. The parent page's campaign context — gclid, UTM parameters, ad click data — never crosses the iframe boundary. GA4 sees no conversion on your page at all, and the scheduler's own analytics sees a session with no source, which typically reports as direct. The fix is a scheduler that fires the confirmation event back on your page, inside your visitor's existing session.
Do I need cross-domain tracking for an embedded scheduler?
Not if the scheduler fires events on the host page. Cross-domain tracking in GA4 only helps when visitors navigate to another domain in the same tab — it's fragile for iframes and usually impossible to configure on a third-party scheduler's property because you don't own it. Cartoply's embed listens for the booking confirmation and pushes it directly to the host page's dataLayer, gtag, and Meta pixel. No cross-domain setup required — it works in the visitor's existing session automatically.
How do I track Google Ads conversions from a booking form?
Fire generate_lead (or a custom event) in GA4 when the booking confirms, mark it as a Key Event in GA4, then import that Key Event into Google Ads as a conversion action. Because Cartoply fires the event in the visitor's own session — the same one that started with the ad click — the gclid is already attached and Google Ads attributes the conversion correctly. No additional tagging is needed beyond the standard Cartoply embed.
What is offline conversion tracking for bookings?
Offline conversion tracking means uploading a conversion to Google Ads by click ID after the fact — for example, when a booked estimate turns into a signed job. Cartoply stores the gclid, fbclid, and full UTM set on every booking and includes them in Zapier payloads. Your CRM receives the click ID at the time of booking, and when the deal closes you upload the final sale value against that gclid. Google Ads then credits the original ad click for the actual revenue, giving you true ROAS — not just cost-per-lead.
Ready to give your clients bookings they can attribute?
14-day free trial — no credit card required. $7/seat/month after that.
Start free trial →Get field service tips in your inbox
Scheduling, dispatch, and territory strategy for home service teams. No spam, unsubscribe anytime.
By subscribing you agree to receive emails from Cartoply. Unsubscribe anytime. See our Privacy Policy.