Tracking
GTM, GA4 and ad conversions
Track Microsoft Bookings conversions with Google Tag Manager, GA4, Google Ads enhanced conversions, Meta Pixel and LinkedIn, plus a thank-you redirect.
Bookings made inside Microsoft's own booking page are invisible to your analytics. With BookingsXP, the loader on your page receives each step of the booking from the widget and passes it to the tools you already run. You do not need to add code for any of this; you only need the tags on your page and, for GA4 and ad platforms, the IDs in your widget's Tracking tab.
| Destination | What fires | Plan |
|---|---|---|
dataLayer (Google Tag Manager) | Every event as bookingsxp.<name> | Free, also in link-only mode |
GA4 via gtag | bookingsxp_view, bookingsxp_slot, bookingsxp_form_start, generate_lead, bookingsxp_booked | Free |
| Thank-you page redirect | Navigates after booking_completed | Free |
| Google Ads | conversion with the booking reference as transaction_id | Pro |
| Google Ads enhanced conversions | SHA-256 of the email, hashed in the browser | Pro |
| Meta Pixel | Schedule on booking, ViewContent on view | Pro |
| LinkedIn Insight Tag | Conversion on booking | Pro |
The loader never loads these vendor tags itself. It calls gtag, fbq or lintrk only if the function already exists on the page and the matching ID is set on the widget. That keeps your consent tool in charge: if it has not loaded a tag, nothing is sent to that vendor. The one exception is the hosted booking page, which loads the tags for the IDs you configured because there is no host page to do it.
Google Tag Manager#
For every event, the loader pushes:
window.dataLayer.push({
event: "bookingsxp.booking_completed",
bookingsxp: {
name: "booking_completed",
widgetId: "w_8fk2m1qz",
businessName: "Contoso Physio",
service: { id: "a1b2c3", name: "Initial consultation", durationMinutes: 45, price: 60, currency: "GBP" },
staff: { id: "d4e5f6", name: "Sam Lee" },
slot: { start: "2026-10-02T14:00:00Z", end: "2026-10-02T14:45:00Z", timeZone: "Europe/London" },
booking: { id: "BXP-7K3M9Q" },
},
});The object is the event without its tracking block. Event names are bookingsxp.widget_viewed, bookingsxp.service_selected, bookingsxp.staff_selected, bookingsxp.date_selected, bookingsxp.slot_selected, bookingsxp.form_started, bookingsxp.booking_submitted, bookingsxp.booking_completed, bookingsxp.booking_failed and bookingsxp.no_availability.
Set up a booking trigger#
- In GTM, go to Triggers → New → Custom Event.
- Event name:
bookingsxp.booking_completed. - Save it as "BookingsXP booking".
To catch every step instead, use the event name ^bookingsxp\. and tick Use regex matching.
Data Layer Variables#
Create these under Variables → New → Data Layer Variable (Data Layer Version 2):
| Variable name | Data Layer Variable Name |
|---|---|
| BXP reference | bookingsxp.booking.id |
| BXP service | bookingsxp.service.name |
| BXP value | bookingsxp.service.price |
| BXP currency | bookingsxp.service.currency |
| BXP start | bookingsxp.slot.start |
| BXP staff | bookingsxp.staff.name |
| BXP widget | bookingsxp.widgetId |
| BXP error | bookingsxp.error.code |
Price and currency are present when the service has a price in Microsoft Bookings.
Example: a GA4 event tag in GTM#
- Tag type: Google Analytics: GA4 Event
- Event name:
generate_lead - Event parameters:
service_name={{BXP service}},value={{BXP value}},currency={{BXP currency}},transaction_id={{BXP reference}} - Trigger: "BookingsXP booking"
If you do this in GTM, leave the GA4 measurement ID on the widget empty, or you will count each booking twice.
GA4 without GTM#
If your page loads GA4 with gtag.js, add your measurement ID (G-…) in the widget's Tracking tab. The loader then sends:
| Widget event | GA4 event |
|---|---|
widget_viewed | bookingsxp_view |
slot_selected | bookingsxp_slot |
form_started | bookingsxp_form_start |
booking_completed | generate_lead and bookingsxp_booked |
Each call looks like this; parameters without a value are left out:
gtag("event", "generate_lead", {
send_to: "G-ABC123XYZ",
service_name: "Initial consultation",
staff_name: "Sam Lee",
value: 60,
currency: "GBP",
});Other widget events are not sent to GA4 directly. Use GTM on the dataLayer for those. In GA4, mark generate_lead (or bookingsxp_booked) as a key event.
Google Ads (Pro)#
Add your conversion ID (AW-123456789, the AW- is added if you leave it out) and conversion label in the Tracking tab. Your page needs the Google tag (gtag.js) with your Ads account configured. On booking_completed the loader sends:
gtag("event", "conversion", {
send_to: "AW-123456789/AbCdEfGhIjKlMnOp",
transaction_id: "BXP-7K3M9Q",
});The booking reference is the transaction_id, so Google Ads de-duplicates a conversion that is reported twice.
Enhanced conversions#
Turn on Enhanced conversions in the Tracking tab to improve matching. The widget hashes the visitor's email with SHA-256 in the browser, lower-cased and trimmed, and the plain email never leaves the widget for this purpose. The loader adds it to the conversion:
gtag("event", "conversion", {
send_to: "AW-123456789/AbCdEfGhIjKlMnOp",
transaction_id: "BXP-7K3M9Q",
user_data: { sha256_email_address: "5b2c…e91f" },
});The hash is also in the dataLayer event as bookingsxp.emailSha256, if you prefer to pass it on from GTM. Mention enhanced conversions in your privacy notice before you turn it on.
Meta Pixel (Pro)#
Add your Pixel ID. With the Meta Pixel on your page, the loader fires:
// on widget_viewed
fbq("track", "ViewContent");
// on booking_completed
fbq("track", "Schedule", {}, { eventID: "BXP-7K3M9Q" });eventID is the booking reference. If you also send Schedule from a server through the Conversions API, use the same reference as event_id and Meta counts it once. You can get the reference server-side from a webhook.
LinkedIn (Pro)#
Add your LinkedIn partner ID and conversion ID. With the Insight Tag on your page, the loader fires on booking_completed:
window.lintrk("track", { conversion_id: 1234567 });Thank-you page redirect#
Set a redirect URL in the Tracking tab, or the redirect-url attribute on the page, and the visitor is sent to your page after booking. Any analytics tool that can count a page view can then count bookings.
<bookingsxp-widget
widget="w_8fk2m1qz"
redirect-url="https://www.example.com/thank-you?ref={booking_id}&service={service}"
></bookingsxp-widget>{booking_id}, {service} and {start} are replaced and URL-encoded. The loader waits 600 ms so pixels finish sending, then navigates the whole page, not just the iframe. Details in Embed attributes.
Attribution in Microsoft Bookings#
Separate from your analytics, BookingsXP writes where the booking came from into the booking notes in Microsoft Bookings, so it shows in Outlook, Teams and the Bookings app:
— Booked on your website · Ref BXP-7K3M9Q
Source: google / cpc · Campaign: brand (Paid search)
Keyword: physio near me
First page: example.com/pricing
Booked from: example.com/book
Referrer: google.com
Ad click: gclidLines appear only when there is something to show. The source comes from UTM parameters first, then ad click IDs (gclid, gbraid, wbraid, msclkid, fbclid, li_fat_id, ttclid), then the referrer. Channels follow GA4's default grouping: Paid search, Paid social, Organic search, Organic social, Email, Referral, Direct and Other. UTM and click-ID parameters are removed from the page paths shown. The block holds no personal data.
The loader keeps the first campaign touch for 90 days in the visitor's browser, so a visitor who clicks an ad on Monday and books on Thursday is still credited to the ad. See Privacy & data for how this works with consent.
Turn it off with Write source & campaign into the booking notes in the Tracking tab. You can also have BookingsXP answer one custom question on your Bookings page (for example "How did you hear about us?") with the source, so it shows in Microsoft's own export.
Checking it works#
- Open your page with
?utm_source=test&utm_medium=cpc&utm_campaign=docson the URL. - In GTM Preview (or the browser console, with
window.dataLayer), watch forbookingsxp.widget_viewed. - Make a test booking. You should see
bookingsxp.booking_completed, the GA4 and pixel calls in the network tab, and the attribution block in the appointment's notes in Microsoft Bookings. - Cancel the test appointment in Microsoft Bookings as usual.
Edit or question? hello@bookingsxp.com