← All Guides

Calendly Booking Tracking with Google Tag Manager: Google Ads Conversions and Enhanced Conversions from an Embedded Widget

How to track completed Calendly bookings from an embedded widget with Google Tag Manager, send them to GA4 and Google Ads, and attach user-provided data for Enhanced Conversions even though the email is typed inside Calendly's iframe.

Atiqul Islam
Written by Atiqul Islam

Tracking & analytics specialist. 278+ GTM, GA4 and server-side tracking projects for clients in the US, UK and Australia.

Rather not do this yourself? I set this up for clients every week. Free 15-minute audit, no obligation.

Book a Free Audit

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:

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

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

  1. GTM Preview on the page with the widget. Walk through the flow and complete a test booking with your own email.
  2. Confirm calendly_step events as you progress and exactly one calendly_booking at the end with both URIs populated.
  3. 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.
  4. Cancel the test booking in Calendly afterwards.

Common Problems

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.

Frequently Asked Questions

Why does my Calendly conversion count clicks on the widget instead of bookings?

Because the trigger is a click or a visibility event on the embed, not the scheduling confirmation. Calendly posts a calendly.event_scheduled message to your page only when a booking completes. Trigger on that message and nothing else.

Can I get the invitee's email for Enhanced Conversions?

Not directly from the confirmation message. Either capture the email on your own page before the widget loads and prefill Calendly with it, or look up the invitee through the Calendly API from a server-side container using the invitee URI included in the message. Both are covered below.

Which Calendly embed types does this work with?

Inline, popup widget and popup text all post the same events to the parent page. Links that open calendly.com in a new tab do not, so use an embed if you want tracking.

Does Calendly have a native Google Ads or GA4 integration?

Calendly offers tracking options on paid plans, but they run inside Calendly's own pages and do not carry your site's Google Ads click identifier. Tracking from the parent page through GTM attributes correctly to your campaigns.

How do I avoid counting the same booking twice?

Calendly fires the scheduled event once per booking. Keep one listener and one trigger, and use the invitee URI as a transaction ID on the Google Ads tag so a repeat is deduplicated.

Want This Done Right the First Time?

Book a free 15-minute audit call. I'll review your setup, show you what is broken, and tell you exactly what it takes to fix it.

Chat Now