Md. Alamin

help businesses fix broken tracking

Enable Calendly DataLayer

If you want to fix your Calendly tracking and capture full user details like Name, Email, Phone Number, then this guide is exactly for you.

Most people embed Calendly but donโ€™t get full booking data inside their tracking tools โ€” Meta Pixel, CAPI, Google Tags, and GTM fail to receive key information.
A missing DataLayer = broken tracking.

In this short guide, I will show you:

โœ” What a DataLayer is
โœ” Why you must enable it for Calendly
โœ” The benefits youโ€™ll get
โœ” The custom JavaScript code (below)
โœ” A video tutorial so you can follow step-by-step

๐ŸŽฅ Watch the Full Video Video To Enable DataLayer

<script>
(function() {
  window.addEventListener("message", function (e) {
    if (!e.data || e.data.event !== "calendly.event_scheduled") return;

    var inviteeUrl = e.data.payload && e.data.payload.invitee ? e.data.payload.invitee.uri : null;
    if (!inviteeUrl) return;

    var token = "{{calendly access token}}";
    var headers = {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + token
    };

    var PHONE_SEPARATORS = "()._-+/ ";

    // Helper functions matching server-side logic
    function toPhoneNumber(val, strict) {
      if (typeof val !== "string") return undefined;
      var digits = "0123456789";
      var hasLeadingPlus = val.trim().indexOf("+") === 0;
      var cleaned = "";
      var onlyPhoneChars = true;

      for (var i = 0; i < val.length; i++) {
        var ch = val.charAt(i);
        if (digits.indexOf(ch) !== -1) {
          cleaned += ch;
        } else if (PHONE_SEPARATORS.indexOf(ch) === -1) {
          onlyPhoneChars = false;
        }
      }

      if (strict && !onlyPhoneChars) return undefined;
      if (cleaned.length < 7 || cleaned.length > 15) return undefined;
      return hasLeadingPlus ? "+" + cleaned : cleaned;
    }

    function buildAnswerMap(qAndA) {
      var map = {};
      if (!qAndA || !Array.isArray(qAndA)) return map;
      qAndA.forEach(function (item) {
        if (typeof item.question !== "string") return;
        var q = item.question.trim();
        var a = typeof item.answer === "string" ? item.answer.trim() : item.answer;
        if (q && a !== undefined && a !== null && map[q] === undefined) {
          map[q] = a;
        }
      });
      return map;
    }

    function getEventUrl(uri) {
      var parts = uri.split("/");
      if (parts.length < 5 || parts[3] !== "scheduled_events" || !parts[4]) return null;
      return "https://api.calendly.com/scheduled_events/" + parts[4];
    }

    function fetchCalendly(url) {
      return fetch(url, { method: "GET", headers: headers })
        .then(function (res) {
          return res.ok ? res.json() : null;
        })
        .then(function (json) {
          return json && json.resource ? json.resource : null;
        })
        .catch(function () {
          return null;
        });
    }

    var eventUrl = getEventUrl(inviteeUrl);

    // Fetch both Invitee and Event in parallel
    Promise.all([
      fetchCalendly(inviteeUrl),
      eventUrl ? fetchCalendly(eventUrl) : Promise.resolve(null)
    ]).then(function (results) {
      var invitee = results[0];
      var eventData = results[1];

      if (!invitee) return;

      var routingPromise = invitee.routing_form_submission
        ? fetchCalendly(invitee.routing_form_submission)
        : Promise.resolve(null);

      routingPromise.then(function (submission) {
        var payload = {
          event: "calendly_booked"
        };

        // 1. Meeting details
        if (eventData) {
          if (eventData.name) payload.name = eventData.name;
          if (eventData.meeting_notes_plain) payload.meeting_notes_plain = eventData.meeting_notes_plain;
          if (eventData.event_guests) payload.event_guests = eventData.event_guests;
          if (eventData.invitees_counter) payload.invitees_counter = eventData.invitees_counter;
          if (eventData.start_time) payload.start_time = eventData.start_time;
          if (eventData.end_time) payload.end_time = eventData.end_time;
          if (eventData.created_at) payload.event_created_at = eventData.created_at;
          if (eventData.updated_at) payload.event_updated_at = eventData.updated_at;
          if (eventData.location) payload.location = eventData.location;
        }

        // 2. Invitee details
        if (invitee.timezone) payload.timezone = invitee.timezone;
        if (invitee.scheduling_method) payload.scheduling_method = invitee.scheduling_method;
        if (invitee.created_at) payload.created_at = invitee.created_at;
        if (invitee.updated_at) payload.updated_at = invitee.updated_at;

        // User Data Structure
        var userData = {};
        var address = {};

        if (invitee.email) {
          var cleanEmail = invitee.email.trim().toLowerCase();
          if (cleanEmail && cleanEmail.indexOf("@") > 0) {
            userData.email_address = cleanEmail;
          }
        }

        // Names
        if (invitee.name) {
          var parts = invitee.name.trim().split(/\s+/);
          if (parts.length > 0) {
            address.first_name = parts[0];
            if (parts.length > 1) {
              address.last_name = parts.slice(1).join(" ");
            }
          }
        }
        if (invitee.first_name) address.first_name = invitee.first_name;
        if (invitee.last_name) address.last_name = invitee.last_name;

        if (Object.keys(address).length > 0) userData.address = address;

        // Form Q&A and Phone Parsing
        if (invitee.questions_and_answers && invitee.questions_and_answers.length > 0) {
          payload.event_questions = [];
          payload.event_answers = [];

          invitee.questions_and_answers.forEach(function (qa) {
            payload.event_questions.push(qa.question);
            payload.event_answers.push(qa.answer);

            if (!userData.phone_number) {
              var extractedPhone = toPhoneNumber(qa.answer, true);
              if (extractedPhone) userData.phone_number = extractedPhone;
            }
          });

          var eventAnsMap = buildAnswerMap(invitee.questions_and_answers);
          if (Object.keys(eventAnsMap).length > 0) {
            payload.event_answers_by_question = eventAnsMap;
          }
        }

        // SMS Reminder Phone Priority
        if (invitee.text_reminder_number) {
          var smsPhone = toPhoneNumber(invitee.text_reminder_number, false);
          if (smsPhone) userData.phone_number = smsPhone;
        }

        if (Object.keys(userData).length > 0) payload.user_data = userData;

        // 3. Tracking (UTMs)
        if (invitee.tracking) {
          var tracking = {};
          var keys = ["utm_campaign", "utm_source", "utm_medium", "utm_content", "utm_term", "salesforce_uuid"];
          keys.forEach(function (k) {
            if (invitee.tracking[k]) tracking[k] = invitee.tracking[k];
          });
          if (Object.keys(tracking).length > 0) payload.tracking = tracking;
        }

        // 4. Payment Data
        if (invitee.payment) {
          var pay = invitee.payment;
          var details = {};
          Object.keys(pay).forEach(function (k) {
            if (k !== "terms" && pay[k] !== null) details[k] = pay[k];
          });
          if (Object.keys(details).length > 0) payload.payment = details;
          if (pay.external_id) payload.transaction_id = pay.external_id;
          if (typeof pay.amount === "number") payload.value = pay.amount;
          if (pay.currency) payload.currency = pay.currency;
          if (typeof pay.successful === "boolean") payload.payment_successful = pay.successful;
        }

        // 5. Routing Form Data
        if (submission && submission.questions_and_answers) {
          payload.routing_form_questions = [];
          payload.routing_form_answers = [];
          submission.questions_and_answers.forEach(function (qa) {
            payload.routing_form_questions.push(qa.question);
            payload.routing_form_answers.push(qa.answer);
          });
          var routeAnsMap = buildAnswerMap(submission.questions_and_answers);
          if (Object.keys(routeAnsMap).length > 0) {
            payload.routing_form_answers_by_question = routeAnsMap;
          }
        }

        // Push unified payload to dataLayer
        window.dataLayer = window.dataLayer || [];
        window.dataLayer.push(payload);
      });
    });
  });
})();
</script>

๐Ÿ“œ Calendly Form Event Tracking Video

This Video covers everything you need to track Calendly bookings using Google Tag Manager (GTM) for both browser-side Pixel tracking and server-side CAPI tracking. Ideal for improving your event match quality, event coverage, deduplication, and ad optimization!

โœ… Key topics covered:

00:00 Intro & Why Accurate Tracking Matters  

01:15 Connect Google Tag Manager to WordPress  

03:10 Enable Calendly DataLayer Push  

05:35 Generate Calendly Personal Access Token  

06:55 Replace Token in GTM Variables  

08:40 Set Up Meta Pixel PageView Event in GTM  

10:30 Set Up Calendly Booking Custom Event in GTM  

12:20 Pass User Info: Email, Phone, Name, External ID  

14:15 Test Pixel Events with Meta Event Manager  

15:30 Why Server-Side CAPI is Important  

16:45 Set Up Server Container in Stape.io  

19:10 Connect Stape.io Subdomain or Use Default URL  

21:00 Set Up Custom Loader for AdBlocker Bypass  

23:00 Create GA4 Measurement ID for Testing  

25:00 Pass External IDs for Deduplication  

26:45 Set Up Conversion API Tags in Server GTM  

29:20 Testing Server-Side Events in Event Manager  

32:10 Final Testing, Troubleshooting & Publishing  

34:30 Important: Remove Test Event Codes Before Ads  

35:00 Service Offer, Free Advice & How to Contact Me

๐ŸŽฅ Watch the Full Video to Track Events

Leave a Reply

Your email address will not be published. Required fields are marked *

Md. Alamin

help businesses fix broken tracking

Chat With Me
Instant Support
Instant Support
Need Help? Let's Chat! ๐Ÿ’ฌ