Consultants, coaches, agencies and clinics run Google Ads to one goal: a booked call. When that call is booked through an embedded Calendly widget, Google Ads usually gets one of two wrong signals. Either nothing, because the booking happens inside an iframe on calendly.com, or a click on the widget, which counts everyone who looked at available times.
This guide shows how to track real Calendly bookings from your own page with Google Tag Manager, send them to GA4 and Google Ads, and pass user-provided data for Enhanced Conversions so Smart Bidding gets the strongest signal available.
How Calendly Talks to Your Page
Calendly's embed posts messages to the parent window as the visitor moves through the flow:
calendly.profile_page_viewedcalendly.event_type_viewedcalendly.date_and_time_selectedcalendly.event_scheduled
The last one is the booking confirmation. It includes a payload with the event URI and the invitee URI. It does not include the invitee's email.
Step 1: The Listener Tag
Create a Custom HTML tag named Listener - Calendly and fire it on All Pages, or on the pages that contain the widget:
<script>
(function () {
window.addEventListener('message', function (e) {
if (!e.origin || e.origin.indexOf('calendly.com') === -1) { return; }
var data = e.data || {};
if (typeof data.event !== 'string' || data.event.indexOf('calendly.') !== 0) { return; }
var payload = data.payload || {};
window.dataLayer = window.dataLayer || [];
if (data.event === 'calendly.event_scheduled') {
window.dataLayer.push({
event: 'calendly_booking',
booking_tool: 'calendly',
calendly_event_uri: payload.event && payload.event.uri ? payload.event.uri : '',
calendly_invitee_uri: payload.invitee && payload.invitee.uri ? payload.invitee.uri : ''
});
} else {
window.dataLayer.push({
event: 'calendly_step',
calendly_step: data.event.replace('calendly.', '')
});
}
});
})();
</script>
The booking event is separate from the step events so you can report on funnel drop-off without ever confusing a step with a conversion.
Step 2: Variables and Triggers
Data Layer Variables: calendly_event_uri, calendly_invitee_uri, calendly_step. A Custom Event trigger named CE - calendly_booking for the conversion. Optionally a Custom Event trigger for calendly_step for GA4 funnel reporting.
Step 3: GA4 and Google Ads Tags
- GA4 Event
generate_leadwithbooking_toolandcalendly_event_urias parameters. Mark it as a key event. - Google Ads Conversion Tracking with your conversion ID and label. Set the Transaction ID field to
calendly_invitee_uriso repeats are deduplicated. Keep a Conversion Linker on All Pages. - Meta Lead or Schedule event if you run Meta ads.
Step 4: Getting the Email for Enhanced Conversions
Two ways, depending on how much you can change.
Option A: capture the email on your page first. Add a short step before the widget: a single email field, or a name and email pair, with a Continue button. When the visitor continues, store the email in a JavaScript variable and load the Calendly embed with prefill parameters so they do not type it twice:
<script>
function openCalendly(email, name) {
window.dataLayer.push({ event: 'lead_prefill', user_email: email.trim().toLowerCase() });
Calendly.initInlineWidget({
url: 'https://calendly.com/your-link',
parentElement: document.getElementById('calendly-inline'),
prefill: { email: email, name: name }
});
}
</script>
Create a User-Provided Data variable in GTM from the user_email Data Layer Variable and attach it to the Google Ads conversion tag. When calendly_booking fires, the email from the earlier step is still in the dataLayer and is hashed and sent with the conversion. This option also gives you a lead even when the visitor abandons the Calendly step.
Option B: look up the invitee server-side. The calendly_invitee_uri in the booking event is a Calendly API resource. If you run server-side Google Tag Manager, send the booking event to your server container, have it call the Calendly API with a personal access token to fetch the invitee, and attach the returned email to the Google Ads conversion as user-provided data from the server. The email never touches the browser. This is the cleaner option for sites that cannot add a pre-step.
Step 5: Turn On Enhanced Conversions in Google Ads
In the conversion action settings, enable Enhanced conversions and choose Google Tag Manager, or API if you used Option B. Diagnostics will show the match rate after a few days.
Step 6: Test
- GTM Preview on the page with the widget. Walk through the flow and complete a test booking with your own email.
- Confirm
calendly_stepevents as you progress and exactly onecalendly_bookingat the end with both URIs populated. - Confirm GA4 and Google Ads tags fired once. Inspect the Google Ads request in a tag debugger such as the Insighter Debugger extension, and confirm the transaction ID and the hashed user data if you used Option A.
- Cancel the test booking in Calendly afterwards.
Common Problems
- No events arrive. The embed is a plain link, not an inline or popup widget, or the origin check does not match. Log messages in the console to confirm.
- Booking fires but no attribution. Conversion Linker missing, or the booking happened on calendly.com in a new tab.
- Enhanced Conversions match rate is zero. The User-Provided Data variable is attached but the email variable is empty at the moment the conversion fires. Check the order of events in Preview.
Outcome
Google Ads receives a conversion only when a call is actually booked, with a transaction ID to prevent duplicates and a hashed email to recover matches that cookies alone would miss. Smart Bidding learns from booked calls, which is what you are paying for.
