Back to the notebook
Acuity + GTM14 min read

How to Track Acuity Scheduling Appointments with Google Tag Manager

A step-by-step guide to connecting embedded Acuity bookings to Google Tag Manager with postMessage, an AcuityEvent data layer event, and reusable conversion triggers.

Written by Abdel · Updated September 3, 2026

If you’re using Acuity Scheduling to book appointments on your website, you may want to track completed bookings as conversions in Google Tag Manager. This lets you measure how many visitors actually schedule appointments, then send the relevant conversions to GA4, Google Ads, Meta, LinkedIn, or another marketing platform.

The challenge is that Acuity can run inside an iframe—or several nested iframes. GTM on your main website does not automatically know when someone completes an appointment.

In this guide, I’ll show you how to connect the two using JavaScript and the browser’s postMessage() method. We’ll push an AcuityEvent event into the GTM data layer after a booking and use it to trigger your conversion tags.

Prefer to watch the video?

Follow along with my complete tutorial: add both scripts, complete a test appointment, find AcuityEvent in GTM Preview Mode, and connect a conversion tag. The written version below includes additional security checks and corrects the calendar field label.

Watch the step-by-step tutorial
01

What we’re going to build

We’ll use two snippets. The first runs in Acuity’s Custom Conversion Tracking section and sends a booking message to the top-level browser window. The second runs in GTM on your main website and translates that message into a data layer event.

Completed bookingAcuity tracking scriptpostMessage()Website listenerAcuityEventConversion tag

Once AcuityEvent reaches GTM, you can use it like any other Custom Event. One booking signal can serve multiple destinations without a separate Acuity integration for each platform.

02

Add the tracking script to Acuity Scheduling

Log in to Acuity and open Integrations → Analytics + Custom Integrations → Custom conversion tracking. Paste this into the HTML tracking code field and save your settings.

Acuity conversion tracking script
<script>
try {
  var postobject = JSON.stringify({
    event: "AcuityEvent",
    Email: "%email%",
    ID: "%id%",
    Type: "%type%",
    AppointmentType: "%appointmentType%",
    Calendar: "%calendar%"
  });

  var targetWindow = window;
  while (targetWindow.parent && targetWindow.parent !== targetWindow) {
    targetWindow = targetWindow.parent;
  }

  // Replace this with the exact origin of your main website.
  targetWindow.postMessage(postobject, "https://www.example.com");
} catch (e) {
  window.console.warn('Acuity tracking message could not be sent.');
}
</script>
Set your real website origin

Replace https://www.example.com with the exact protocol and hostname of the website containing the scheduler—no path or trailing slash. The www and non-www versions are different origins. Do not replace it with * when sending customer information.

Acuity also runs conversion tracking for packages, gift certificates, and subscriptions. The listener below filters for appointments so those purchases do not become appointment leads. See Acuity’s conversion tracking documentation for supported variables and setup details.

03

Understand the Acuity script

The script builds an object from Acuity’s dynamic values, then converts it to a JSON string using JSON.stringify().

Acuity message fields
{
  event: "AcuityEvent",
  Email: "%email%",
  ID: "%id%",
  Type: "%type%",
  AppointmentType: "%appointmentType%",
  Calendar: "%calendar%"
}
VariableWhat it represents
%id%Appointment ID
%type%Appointment or order
%appointmentType%Appointment type
%calendar%Calendar name—not the customer’s full name
%email%Client email address

Find the top-level window

Walk up the window hierarchy
var targetWindow = window;
while (targetWindow.parent && targetWindow.parent !== targetWindow) {
  targetWindow = targetWindow.parent;
}

Rather than assuming a fixed iframe depth, this loop follows parent windows until it reaches the top. At that point, targetWindow.parent === targetWindow. It then sends the serialized event to the configured website origin.

Send to your website origin
targetWindow.postMessage(postobject, "https://www.example.com");

These placeholders are inserted into JavaScript strings. Test appointment and calendar names containing quotes, backslashes, and line breaks; JSON.stringify() does not repair a string literal that was broken by substitution before the script ran.

04

Add the listener to Google Tag Manager

Open GTM and go to Tags → New → Tag Configuration → Custom HTML. Paste this listener:

GTM Acuity message listener
<script>
(function () {
  // Prevent duplicate listeners if this tag fires more than once.
  if (window.__acuityListenerInstalled) return;
  window.__acuityListenerInstalled = true;

  var seenAppointments = Object.create(null);

  window.addEventListener('message', function (ev) {
    // Acuity's documented conversion-tracking frame origin.
    // Confirm this origin in your own booking setup before publishing.
    if (ev.origin !== 'https://sandbox.acuityinnovation.com') return;

    try {
      var data = typeof ev.data === 'string'
        ? JSON.parse(ev.data)
        : ev.data;

      if (!data || data.event !== 'AcuityEvent') return;
      if (typeof data.Type !== 'string' ||
          data.Type.toLowerCase() !== 'appointment') return;
      if (typeof data.ID !== 'string' || !data.ID ||
          data.ID === '%id%') return;
      if (typeof data.AppointmentType !== 'string') return;
      if (seenAppointments[data.ID]) return;

      seenAppointments[data.ID] = true;
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: 'AcuityEvent',
        ID: data.ID,
        appointmentType: data.AppointmentType,
        calendar: data.Calendar
      });
    } catch (e) {
      // Ignore unrelated or malformed messages.
    }
  });
})();
</script>
Tag typeCustom HTMLTriggerPages containing your Acuity schedulerSuggested nameHTML - Acuity Appointment Listener

If the scheduler appears throughout your website, use All Pages. Make sure the listener loads before the booking is completed, then save the tag.

How the listener works

window.addEventListener('message', ...) receives messages. The listener checks the sender’s origin, parses string messages, and accepts only an AcuityEvent with an appointment ID and appointment type. It then pushes the event into window.dataLayer.

The guard at the top prevents multiple listener registrations. The ID check suppresses repeated messages for the same appointment during this page load; it does not provide deduplication across reloads or devices.

Keep the origin check

A matching event name alone does not prove who sent a message. Confirm the conversion frame’s exact origin in your real setup; do not accept arbitrary origins or blindly allow null. For additional validation, check ev.source against a known frame when your embedding structure allows it. See MDN’s postMessage security guidance.

05

Test the Acuity appointment in GTM Preview Mode

Click Preview in GTM, enter your website URL, and connect Tag Assistant. On your website, select an appointment type, choose a date and time, enter test details, and complete the booking.

Return to Tag Assistant and look for AcuityEvent in the timeline. Select it and open the Data Layer tab. With the listener above, you should see something like:

Example booking event in GTM
{
  event: "AcuityEvent",
  ID: "123456789",
  appointmentType: "Free Consultation",
  calendar: "Consultations"
}

The values here are examples, not real bookings. Seeing this event confirms that the message reached GTM; it does not yet confirm delivery to an analytics or advertising platform. Use an approved test appointment and remember to cancel it afterward if needed.

06

Create an Acuity Custom Event trigger

Go to Triggers → New → Trigger Configuration → Custom Event. Enter AcuityEvent exactly, including capitalization, choose All Custom Events, and save.

Event nameAcuityEventTrigger nameCE - Acuity Appointment Completed

You now have a reusable GTM trigger representing the appointment message accepted by your listener.

07

Use AcuityEvent to trigger a conversion

Attach your new trigger to the appropriate conversion tag. Examples include a GA4 generate_lead event, a Google Ads conversion, a Meta Lead event, a LinkedIn conversion, or a TikTok event.

AcuityEventGTM Custom Event triggerYour conversion tag

Configure each destination separately, including its base tag, consent settings, and required parameters. Do not forward the whole message object automatically.

Test the full path before publishing

Open GTM Preview again and complete another test booking. Select AcuityEvent. Your conversion tag should appear under Tags Fired:

Example Tag Assistant result
AcuityEvent
Tags Fired:
  GA4 - generate_lead

Or, for a configured Meta implementation:
  Meta - Lead

A base tag may have fired earlier on page load; it does not need to appear on this same event. Also verify receipt in the destination platform’s debugging tools. “Tags Fired” means GTM ran the tag, not that the platform necessarily accepted the conversion.

08

Track different Acuity appointment types

A free consultation, product demo, discovery call, and existing-client meeting may have different business value. You do not have to count all of them as the same conversion.

Create a Data Layer Variable using Variables → New → Data Layer Variable. Enter appointmentType and name it DLV - Acuity Appointment Type.

Change the relevant Custom Event trigger to Some Custom Events and add a matching condition:

Appointment-specific trigger
Event = AcuityEvent
AND
DLV - Acuity Appointment Type equals Free Consultation

Repeat with the exact type names shown in your Data Layer tab. For example, send free consultations to your lead conversion and track existing-client meetings separately.

09

Use the Acuity appointment ID

Create another Data Layer Variable with the name ID and label it DLV - Acuity Appointment ID. The capitalization must match the listener.

Appointment identifier
ID: data.ID

A stable appointment ID can help connect a browser event to backend data or support a destination’s deduplication mechanism. Mapping it into an arbitrary event parameter does not automatically deduplicate anything—follow that platform’s rules.

Be careful with email addresses and PII

The sender demonstrates Email: "%email%", but basic conversion tracking normally needs only the event name, ID, and appointment type. Remove the Email line from the Acuity script if you do not need it. The default listener does not push email or a customer name.

Do not send raw email addresses as ordinary GA4 event parameters or forward personal data indiscriminately to advertising platforms. If you have a permitted enhanced-conversion or CRM use case, use its dedicated user-data fields and meet the destination’s consent, formatting, and hashing requirements.

Calendar names and appointment types can also reveal sensitive information, particularly for health services. Review them before sending them to analytics. Data in a browser data layer is accessible to other scripts on the page.

For a minimal data layer event, use:

Minimal appointment event
window.dataLayer.push({
  event: data.event,
  ID: data.ID,
  appointmentType: data.AppointmentType
});
10

Why find the top-level window and use postMessage()?

An embedded scheduler may sit directly inside your website, or inside a widget containing additional frames. Hardcoding window.parent.parent assumes exactly two parent levels. Walking through the parent hierarchy avoids that assumption.

Example nested-frame structure
Main website (GTM listener)
  └─ Scheduler / widget frame
      └─ Acuity conversion tracking frame

Conversion message → top-level website → dataLayer

Browsers restrict direct access between different origins. A normal GTM form trigger cannot simply reach inside a third-party scheduler. postMessage() gives the two contexts an explicit communication channel without reading the iframe’s internal page.

This method assumes the top-level page is your website and contains the listener. A booking opened directly on Acuity, in a new tab, or inside another top-level host needs a different integration or a deliberately chosen receiving window.

Why not just track the schedule button?

A visitor can click “Book a Call,” inspect the available dates, and leave without booking. A click measures intent, not an appointment. Here, the conversion message originates from Acuity’s completed-booking flow, providing a more meaningful signal.

11

Troubleshooting Acuity tracking

Confirm the Acuity script is installed

Check Custom Conversion Tracking and make sure the code and settings were saved. Verify that the example receiving origin has been replaced with your actual website origin.

Confirm the GTM listener fired

In Preview Mode, check the page event before the appointment was completed. If the Custom HTML listener did not load, it cannot receive the message.

Complete the entire booking

Opening the scheduler or selecting a date is not enough. Finish an appointment and confirm the booking in Acuity. Test an order separately to make sure it is not counted as an appointment.

Check the message origin and payload

Compare the conversion frame’s origin with the allowed origin in the listener. Check for unresolved placeholders, malformed strings, or missing IDs. Do not remove origin validation just to make the event appear.

Separate message problems from tag problems

If AcuityEvent never appears, troubleshoot the sender, window hierarchy, origin, and listener. If it appears but your conversion tag does not fire, inspect the GTM trigger and consent configuration. If the tag fires but the destination shows nothing, debug the destination request and configuration.

Check duplicate and sensitive events

Test reloads and repeated confirmation views. The example only suppresses repeat IDs in the current page session. Check existing native integrations too, so a single appointment does not send duplicate conversions. Review all outgoing fields before publishing.

Final tracking architecture

One completed booking. One reusable event.

Acuity sends the appointment message, your website validates it, and GTM decides which destinations receive a conversion. That separation makes debugging clearer and avoids rebuilding the same bridge for every marketing platform.

Conclusion

Tracking Acuity appointments takes a different approach from tracking a normal form, but the pieces are straightforward once the sender and listener agree.

  1. Add the Acuity conversion script and set your website’s exact origin.
  2. Install the GTM message listener on the page containing your scheduler.
  3. Complete a test appointment and verify AcuityEvent.
  4. Create a Custom Event trigger using that exact event name.
  5. Connect the GA4, Google Ads, Meta, or other conversion tags you need.
  6. Use appointment type and ID variables where relevant.
  7. Check consent, duplicates, failure paths, and destination receipt.
  8. Publish your GTM container once the implementation is validated.

For a visual walkthrough of the original implementation, watch my complete Acuity + GTM tutorial.

Watch the step-by-step tutorial