Track Calendly meetings with Google Tag Manager Web and Server

Updated: Saturday, August 29, 2026

The Calendly meeting booking tool

Calendly is a free tool that lets your prospects book meetings online. It competes with Cal.com or Hubspot Meetings.

When you use Calendly, you can either use a Calendly page that displays your calendar, or embed it directly on your website through an iframe.

The GTM Web part of this article (the listener that detects the booking) only works if you have embedded Calendly on your website through an iframe (Inline embed, Popup widget or Popup text).

If you send your prospects straight to a Calendly page, this part does not apply, but the GTM Server variable described below also works from Calendly webhooks (see the “What about without an iframe?” callout).

Different ways to embed Calendly on a website through an iframe
Different ways to embed Calendly on a website through an iframe

I will now show you how to track Calendly meeting bookings and how to retrieve the data of the person who booked the meeting (email address, phone number, name, but also UTM parameters, booking form answers and payment data) to send it to advertising platforms.

Data flow overview

The diagram below gives an overview of the data flow that happens when a Calendly meeting is booked.

Data flow diagram of a Calendly booking: from the website to GTM Web, GTM Server, the Calendly API and the advertising platforms
The data flow of a Calendly booking

Install the Calendly listener in GTM Web

To listen to meeting bookings, you need to add this code in Google Tag Manager with a Custom HTML tag.

This is the classic listener that is easy to find online. It does not collect any user data, only the invitee URI (the person who booked a meeting). We will then use this URI in GTM Server to retrieve the information filled in by the invitee.

<script>
window.addEventListener('message', function(e) {
    if (e.data.event && e.data.event.indexOf('calendly.') === 0) {
        if (e.data.event === 'calendly.event_scheduled') {
            window.dataLayer = window.dataLayer || [];
            window.dataLayer.push({
                event: 'calendly_event_scheduled',
                invitee_uri: e.data.payload.invitee.uri
            });
        }
    }
});
/*
* v0.1.0
* Created by Data Marketing School at http://www.data-marketing-school.com
* Written by https://www.linkedin.com/in/lucasrollin/
*/
</script>

Copy the code above and paste it into a Custom HTML tag in GTM Web.

Custom HTML tag to add the Calendly listener
Custom HTML tag to add the Calendly listener

My recommendation

I recommend firing this tag only on the page(s) that contain the Calendly iframe, using the Page path to filter the pages.

Example of a Calendly meeting booking

When a meeting is booked, the calendly_event_scheduled event is pushed to the dataLayer.

calendly_event_scheduled event in the dataLayer containing the invitee URI
calendly_event_scheduled event in the dataLayer containing the invitee URI

At this point, you have validated that the Calendly listener works properly and that the calendly_event_scheduled event is pushed to the dataLayer with the invitee URI.

The invitee URI always has this shape: https://api.calendly.com/scheduled_events/{event_uuid}/invitees/{invitee_uuid}. It contains both the meeting id and the invitee id, which is all the GTM Server variable needs.

dataLayer variable for the invitee URI

The invitee URI will be used server-side to retrieve the invitee’s data.

But before that, you need to create a dataLayer variable in GTM Web to access this value.

dataLayer variable to retrieve the invitee URI in GTM Web
dataLayer variable to retrieve the invitee URI in GTM Web

GA4 data flow

Google tag for GA4

In GTM Web, create a Google tag for your GA4 stream and fill in the measurement id of your GA4 property. If you don’t have one, or don’t plan to send data to GA4, you can use G-XXX.

In Configuration settings, fill in the server_container_url and send_page_view parameters.

If you haven’t set up Google Tag Manager Server for your website tracking yet, check out my dedicated article on the topic or book a discovery call with me.

Google tag for GA4 in GTM Web
Google tag for GA4 in GTM Web

GA4 event calendly_event_scheduled

Still in GTM Web, you then need to create a GA4 event tag to send the calendly_event_scheduled event to your GTM server.

To do so, fill in the server_container_url parameter with the URL of your GTM server.

If you haven’t set up Google Tag Manager Server for your website tracking yet, check out my dedicated article on the topic or book a discovery call with me.

GA4 event calendly_event_scheduled sent to the server from GTM Web
GA4 event calendly_event_scheduled sent to the server from GTM Web

Calendly variable in GTM Server

I developed a GTM Server variable, Enrich from Calendly API by DMS, that queries the Calendly API from the invitee URI and returns a single object gathering everything Calendly knows about the booking:

  • the meeting details (event type name, start and end time, location, guests)
  • the invitee’s user data (email, phone number, first and last name), already formatted for Google Ads, Meta or GA4 tags
  • the UTM parameters passed to Calendly (utm_source, utm_medium, utm_campaign, etc.)
  • the booking form answers and, when applicable, the routing form answers
  • the payment data when the meeting is paid (amount, currency, transaction id)
Enrich from Calendly API by DMS variable
Enrich from Calendly API by DMS variable

Concretely, the variable makes up to three calls to the Calendly API: one for the meeting, one for the invitee and, if the invitee went through a routing form, one for their answers. Only the invitee call is mandatory: if the others fail, the variable still returns the rest of the data and logs the reason in the preview console.

Diagram of the calls made by the Enrich from Calendly API by DMS variable to the Calendly API and of the returned object
What the Enrich from Calendly API by DMS variable does

Here is the shape of the object returned by the variable (keys missing in Calendly are simply omitted):

{
    // Meeting details
    name: "30 Minute Meeting",
    start_time: "2026-08-31T07:00:00Z",
    end_time: "2026-08-31T07:30:00Z",
    location: { type: "google_conference", join_url: "https://..." },
    invitees_counter: { total: 1, active: 1, limit: 1 },
    event_created_at: "2026-08-29T15:49:46Z",
    event_updated_at: "2026-08-29T15:49:50Z",

    // Invitee
    timezone: "Europe/Paris",
    created_at: "2026-08-29T15:49:46Z",
    updated_at: "2026-08-29T15:49:46Z",
    user_data: {
        email_address: "[email protected]",
        phone_number: "+33612345678", // if available
        address: {
            first_name: "John",
            last_name: "Doe"
        }
    },

    // UTM parameters passed to Calendly (if present)
    tracking: {
        utm_source: "newsletter",
        utm_medium: "email",
        utm_campaign: "webinar"
    },

    // Booking form answers
    event_questions: ["How did you hear about us?"],
    event_answers: ["A newsletter"],
    event_answers_by_question: { "How did you hear about us?": "A newsletter" },

    // Routing form answers (if the invitee went through a routing form)
    routing_form_questions: ["What is your budget?"],
    routing_form_answers: ["50k"],
    routing_form_answers_by_question: { "What is your budget?": "50k" },

    // Paid meetings only
    transaction_id: "ch_3Q7xKjL2eZvKYlo2",
    value: 150,
    currency: "EUR",
    payment_successful: true,
    payment: { provider: "stripe", amount: 150, currency: "EUR", ... }
}

A few details about how the user data is formatted:

  • the email is cleaned up (spaces removed, lowercased), as expected by the conversion APIs
  • the phone number is retrieved when available (SMS reminder or booking form answer)
  • the first name and last name come from Calendly’s dedicated fields, or are split from the full name otherwise (a composed last name such as “de la Fontaine” is kept in full)
  • for a paid meeting, transaction_id, value and currency are lifted to the top level so they can be used directly in a conversion tag

What about without an iframe?

The variable also works from Calendly webhooks. If you receive the invitee.created or invitee.canceled notifications in a client of your server container, the webhook’s payload.uri value is exactly the URI expected by the Invitee URI field. The rest of the setup is identical.

Import the variable from the GTM Server gallery

Go to the Variables section of GTM Server and click New.

Creating a new variable in GTM Server
Creating a new variable in GTM Server

Then click the blue banner at the top to open the variable template gallery.

Variable template gallery in GTM Server
Variable template gallery in GTM Server

Search for Calendly and click the Enrich from Calendly API by DMS template by Data Marketing School.

Searching for the Enrich from Calendly API by DMS template
Searching for the Enrich from Calendly API by DMS template

Then click Add to workspace to import the template into your GTM Server container.

Importing the Enrich from Calendly API by DMS template into GTM Server
Importing the Enrich from Calendly API by DMS template into GTM Server

Create a Calendly variable from the template

Go to the Variables section of GTM Server and click New to create a new variable.

Creating a new variable in GTM Server
Creating a new variable in GTM Server

Select the Enrich from Calendly API by DMS variable you just imported.

Selecting the Calendly variable template in GTM Server
Selecting the Calendly variable template in GTM Server

Once the variable is selected, you can see that there are 2 fields to fill in:

  • Personal Access Token: a token you need to generate in your Calendly account so the variable can authenticate against the Calendly API.
  • Invitee URI: an Event data variable you need to create in GTM Server to read the invitee_uri parameter sent by GTM Web.
Calendly variable template selected in GTM Server
Calendly variable template selected in GTM Server

Get the Personal Access Token in Calendly

In your Calendly account, go to the Integrations & apps section and click API and webhooks.

Integrations and apps section in Calendly
Integrations and apps section in Calendly

Then click Generate New Token in the Personal Access Token section.

Generating a Personal Access Token in Calendly
Generating a Personal Access Token in Calendly

When creating the token, Calendly asks you to choose the token’s scopes (permissions). Two scopes are useful for this variable:

  • scheduled_events:read: required, it allows reading the meeting and the invitee
  • routing_forms:read: optional, it allows reading the routing form answers. Without this scope, the variable still returns all the other data and simply reports the routing form error in the preview console
Selecting the Personal Access Token scopes in Calendly
Selecting the Personal Access Token scopes in Calendly

Several hosts in your Calendly organization?

A token created by a regular member only reads that member’s own meetings. If you want to enrich bookings from several hosts (several sales reps, for instance), create the token with an admin or owner account of the organization. Otherwise some invitees will return a 403 error and others won’t, which is quite confusing to debug.

Then copy the generated token.

Personal Access Token copied in Calendly
Personal Access Token copied in Calendly

Then create a Constant variable to store this token in GTM Server.

Personal Access Token pasted into the Calendly variable in GTM Server
Personal Access Token pasted into the Calendly variable in GTM Server

Create the invitee_uri Event data variable

Next, you need to create an Event data variable to retrieve the invitee URI pushed to the dataLayer by GTM Web when a meeting is booked.

Event data variable to retrieve the invitee URI in GTM Server
Event data variable to retrieve the invitee URI in GTM Server

Complete configuration of the Calendly variable

You now have everything you need to configure the Calendly variable in GTM Server. All that’s left is to fill in the Personal Access Token field with the Constant variable you created to store the token, and the Invitee URI field with the Event data variable you just created.

Enrich from Calendly API by DMS variable configured
Enrich from Calendly API by DMS variable configured

Extract the user data from the Calendly object

The Calendly variable returns a complete object, but Google Ads, Meta or GA4 tags only expect the user_data object in the event’s user_data parameter. You therefore need to extract this property before injecting it into the event.

To do so, import the Object Property Extractor variable template from the GTM Server variable template gallery, the same way you did for the Calendly variable.

Object Property Extractor variable to extract user_data from the Calendly object
Object Property Extractor variable to extract user_data from the Calendly object

Then create a variable from this template. It has 2 fields:

  • Object: select the Calendly variable you just configured
  • Property: enter user_data

The Property field accepts dot notation to reach a nested property, for instance user_data.email_address if you only want the email address.

Object Property Extractor variable to extract user_data from the Calendly object
Object Property Extractor variable to extract user_data from the Calendly object

This variable only returns the user_data part of the Calendly object:

{
    email_address: "[email protected]",
    phone_number: "+33612345678", // if available
    address: {
        first_name: "John",
        last_name: "Doe"
    }
}

Not only user_data

This method works for any property of the Calendly object. Create as many Object Property Extractor variables as you need by simply changing the Property field: tracking for the UTM parameters, event_answers_by_question for the form answers, value for the amount of a paid meeting, start_time for the meeting date, etc.

Enrich the calendly_event_scheduled event in GTM Server

The last configuration step consists in adding the Calendly data to the GA4 calendly_event_scheduled event when it is received in GTM Server. I’m using the user_data parameter as an example here, which will contain the invitee’s user data extracted with the Object Property Extractor variable you just created. But this is only an example: you can add any other data from the Calendly object the same way (UTM parameters, booking form or routing form answers, meeting amount, etc.), each with its own Object Property Extractor variable.

This is done by creating a Transformation of type Augment event.

In GTM Server, go to the Transformations section and click New to create a new transformation.

Transformations section in GTM Server
Transformations section in GTM Server

Then add a transformation of type Augment event.

Augment Event transformation in GTM Server
Augment Event transformation in GTM Server

Then configure the transformation so that it applies when the calendly_event_scheduled event is received. In the Parameters to augment section, add a new user_data field and set its value to the Object Property Extractor variable you configured earlier. Add as many fields as there are Calendly data you want to pass to your tags, one per Object Property Extractor variable.

Augment Event transformation configuration to enrich the calendly_event_scheduled event with the invitee's user data
Augment Event transformation configuration to enrich the calendly_event_scheduled event with the invitee's user data

Check the configuration

GTM Web preview

In the GTM Web preview, you can check two things:

  1. the calendly_event_scheduled event is pushed to the dataLayer when a Calendly meeting is booked
  2. the calendly_event_scheduled event is sent to the GTM server by the GA4 - Event - calendly_event_scheduled event tag you configured earlier
GTM Web preview to check that the calendly_event_scheduled event is pushed to the dataLayer and sent to the server
GTM Web preview to check that the calendly_event_scheduled event is pushed to the dataLayer and sent to the server

GTM Server preview

In the GTM Server preview, you can check that the calendly_event_scheduled event is received and that it contains the invitee_uri parameter.

GTM Server preview to check that the calendly_event_scheduled event is received with the invitee_uri parameter
GTM Server preview to check that the calendly_event_scheduled event is received with the invitee_uri parameter

In the Variables tab, also check that your Calendly variable is populated with the complete object (meeting details, user_data, tracking, form answers) and that the Object Property Extractor variable only contains the user_data part.

GTM Server preview to check that the Calendly variable is populated with the invitee's data
GTM Server preview to check that the Calendly variable is populated with the invitee's data

If the Calendly variable is undefined, open the Console tab of the preview: the variable logs the reason for the failure there. The most common cases are:

  • Invalid URI: the invitee_uri parameter does not have the shape https://api.calendly.com/scheduled_events/{event_uuid}/invitees/{invitee_uuid}. Check the Event data variable and the GTM Web listener.
  • 401: the Personal Access Token is missing, incorrect or has been revoked.
  • 403: the token lacks the scheduled_events:read scope, or it was created by a member who is not the host of this meeting (see the callout about the admin role above).
  • 404: the invitee no longer exists, for instance after a reschedule, which creates a new invitee with a new URI.

All the tags (Meta, Google Ads, etc.) you fire when the calendly_event_scheduled event is received in GTM Server can now access the invitee’s user data through the user_data parameter we added in the Augment Event transformation.

FAQ

Partially. The GTM Web listener is enough to send a "meeting booked" event to GA4 or Google Ads, but without any data about the invitee. To retrieve the email, phone number, UTM parameters or the meeting amount, you need to query the Calendly API with a secret token, which must never happen in the browser. That is why this part runs in GTM Server.
Not for the web part: the listener listens to the messages sent by the Calendly iframe embedded on your website. If you send your prospects straight to a Calendly page, you have two options: embed Calendly on your website (Inline embed, Popup widget or Popup text), or use the Calendly webhooks received by a GTM Server client. The Enrich from Calendly API by DMS variable works with both.
The Personal Access Token is available on every Calendly plan, including the free one: the invitee's email, name and phone number come through without a subscription. Routing forms and paid meetings, on the other hand, are features of Calendly's paid plans.
Everything Calendly exposes through its API for this meeting: email, first name, last name, phone number, invitee timezone, meeting name, date and location, UTM parameters, booking form and routing form answers, and payment data (amount, currency, transaction id) for a paid meeting.
The retrieved data is personal data. Sending it to Google Ads, Meta or any other tool must rely on the visitor's consent and be mentioned in your privacy policy. The Calendly token, which gives access to every meeting of your organization, stays server-side in a GTM Server Constant variable and must never be placed in GTM Web.

Congratulations

You have now set up Calendly meeting tracking with Google Tag Manager Web and Server. You can now track meeting bookings and send your invitees’ user data to advertising platforms, and even use the UTM parameters, the form answers or the amount of paid meetings to go further.

Let's talk about your tracking

A question about this article, need an audit of your setup or server-side migration? Drop me a line, I reply within 24h.