Google Tag Manager Integration Guide

Send booking and cart events to GA4 — plus a Custom HTML tag install if you need one.

Medium10 minPro (events)Needs integration

QuackChat with Google Tag Manager

Your chatbot already records booking clicks, confirmed bookings, product clicks and add-to-carts — but in your QuackChat dashboard. This guide pushes those same moments into your Google Analytics, so the chat shows up alongside everything else you measure.

There's also a way to install the widget itself through GTM, if you can't add a plugin or a script tag. That's Part B, and most people won't need it.

Quick Start (TL;DR)

  1. Already installed the widget? Go straight to Part A — the events are already firing, you just need to catch them.
  2. In GTM, add a Data Layer Variable for each field you want (e.g. quackchat_value).
  3. Add a Custom Event trigger matching the event name (e.g. quackchat_add_to_cart).
  4. Add a GA4 Event tag on that trigger. Publish.

Prerequisites

  • The QuackChat widget live on your site — via the WordPress plugin (easiest), a script tag, or Part B below
  • A Google Tag Manager container on your site
  • A GA4 property, if you want the events forwarded there

Part A: Send QuackChat events to GA4

These fire no matter how the widget was installed — WordPress plugin, script tag or GTM. If you're already running the plugin, you're in the right place and can ignore Part B entirely.

Events need a connected integration. Booking events need a connected practice management system; cart events need a connected WooCommerce store. Both are Pro features — on Free and Starter, no QuackChat events fire at all.

What every event carries

Whichever event fires, these five fields come with it:

  • event — the event name, which is what your GTM trigger matches on
  • quackchat_event_id — unique for each event, handy for de-duplicating
  • quackchat_bot_id — which bot it came from
  • quackchat_source — always widget
  • quackchat_integration — which system it came from, e.g. cliniko or woocommerce

The four events

EventFires when
quackchat_booking_link_clickedsomeone clicks the booking link in the chat
quackchat_booking_completeda booking is actually confirmed
quackchat_product_clickedsomeone clicks a product link in the chat
quackchat_add_to_cartsomeone adds a product to their cart from the chat

No extra fields. Booking events deliberately carry nothing about the appointment — no practitioner, service or time — so nothing sensitive ever reaches your analytics.

Needs: a practice management connection with a booking URL set. If your clinic books through the chat only and has no online booking portal configured, this one never fires.

quackchat_booking_completed

No extra fields, same reason as above.

Needs: a connection in direct-booking mode. It fires only when a booking is genuinely confirmed — not when the confirmation card is shown, and it is not re-sent if the customer cancels or the card expires. So this counts real bookings, not intent.

quackchat_product_clicked

  • quackchat_product_id — the product, when we know which card was clicked. Empty for a plain product link in the bot's reply.
  • quackchat_product_url — the product page. Empty if the link doesn't resolve to your store.

Both can legitimately be empty. That's not a fault — treat them as optional in GTM.

quackchat_add_to_cart

  • quackchat_product_id — the product added
  • quackchat_quantity — always 1 today
  • quackchat_value — the item price
  • quackchat_currency — your store's currency
  • quackchat_placementcard for a product card, cross_sell for a "these go well together" suggestion

quackchat_value can be empty. Both the value and currency fields are always sent, but the value stays empty until your store's currency has synced. Don't build a trigger that only checks whether the field exists — it always does. Check that it has a value, or set a fallback in GA4.

Step 1: Create the Data Layer Variables

For each field you want to use, in GTM go to Variables → User-Defined Variables → New → Data Layer Variable and set the Data Layer Variable Name to the field name exactly, e.g. quackchat_value. Name the variable something you'll recognise, like DLV - quackchat_value.

Step 2: Create a Custom Event trigger

Triggers → New → Custom Event. Set Event name to the exact event, e.g. quackchat_add_to_cart. Leave it on "All Custom Events" unless you want to narrow it to certain pages.

Step 3: Create a GA4 event tag

Tags → New → Google Analytics: GA4 Event, pick your measurement ID, give the event a name, and map the fields as event parameters using the variables from Step 1. Attach the trigger from Step 2, then Submit → Publish.

Don't reuse GA4's own event names

Our events are named quackchat_add_to_cart, not add_to_cart, on purpose. Most WooCommerce stores already send the standard names from their theme or a Woo-to-GA4 plugin — reusing them would double-count every sale on exactly the stores that measure most carefully. Map ours to whatever GA4 event you want inside GTM, where you can see and control the mapping.

If you use Consent Mode or a cookie banner:

  • Do gate the GA4 tags above on analytics consent.
  • Don't gate the widget tag from Part B the same way. A support chat is a functional part of your site, and gating it means visitors get no chat at all until they accept marketing cookies.

Clinic bots: appointment data in GA4

If you run a health or medical practice, think before forwarding quackchat_booking_completed to GA4 and on to Google Ads. The event carries no patient details — but "this visitor booked an appointment at a physio clinic" is still health-adjacent information once it's tied to an analytics profile, and Google's terms restrict advertising on sensitive categories. Worth a conversation with whoever runs your ad account.

Not using GTM?

Every event is also fired as a browser event called quackchat:event on the page, so Segment or your own script can listen for it without GTM. The fields arrive on event.detail:

js
window.addEventListener('quackchat:event', (e) => {
  console.log(e.detail.event, e.detail);
});

Part B: Install the widget with GTM

On WordPress, use the QuackChat plugin instead. It's fewer moving parts, it survives theme updates, and — the deciding factor — it keeps working for visitors whose ad blocker blocks Google Tag Manager. Installing the chat through GTM means those visitors get no chat at all. Use this section only if you can't install plugins, or you're not on WordPress.

Step 1: Create the Custom HTML tag

Tags → New → Custom HTML, name it QuackChat Widget, and paste this, replacing YOUR_BOT_ID with the Bot ID from your dashboard's Deploy page:

html
<script>
  (function () {
    if (document.querySelector('script[src*="quackchat-widget"]')) return;
    var s = document.createElement('script');
    s.src = 'https://www.quackchat.app/widget/quackchat-widget.js';
    s.setAttribute('data-bot-id', 'YOUR_BOT_ID');
    s.setAttribute('data-position', 'bottom-right');
    s.async = true;
    (document.body || document.head || document.documentElement).appendChild(s);
  })();
</script>

Leave "Support document.write" unchecked. Under Advanced Settings → Tag firing options, choose Once per page.

The first line is a safety check so the widget can't load twice if it's ever also installed another way.

Keep the address as www.quackchat.app. The version without www redirects, and the widget works out where to send its messages from the address it was loaded from — so every later request goes through that redirect too, not just the first download. Use www and there's no hop to worry about.

Never put a GTM {{Variable}} inside the quoted text above. Anything in your container would be pasted straight into the page as code.

Step 2: Trigger on DOM Ready

Triggering → DOM Ready, All Pages.

Use DOM Ready, not Page View. The safety check on the tag's first line can only see scripts already on the page. Most containers load in the <head>, so Page View fires before the rest of the page exists — including the WordPress plugin's own tag, which is added at the very bottom. The check would find nothing, and you'd get the two chat bubbles it's there to prevent.

Step 3: Publish

Submit → Publish, then confirm with the Verification steps below.

One install only

The first line of the tag is a safety check: if QuackChat is already on the page — say the WordPress plugin is active — this tag notices and does nothing. That's deliberate, so adding it can't give you two chat windows. It also means that if you install this tag and keep the plugin, the tag is doing nothing and the plugin is doing the work. Pick one and remove the other, so the next person to look isn't guessing which one is live.

Verification

Verify the install

Checking the page source won't work — GTM adds the widget after the page loads, so it isn't in the HTML you'd see with "View Source".

  1. Use GTM's Preview mode on your site and confirm the QuackChat Widget tag fired once, on DOM Ready.
  2. Look for the chat bubble on the page (try an incognito window).
  3. In your browser's Network tab, quackchat-widget.js should load with status 200 from www.quackchat.app.

Verify the events

  1. Open GTM Preview and use the chat: click the booking link, or add a product to your cart.
  2. In the Preview panel, the event appears in the left-hand list by name, e.g. quackchat_add_to_cart.
  3. Click it and check the Data Layer tab to see the fields.
  4. In GA4, use Admin → DebugView to confirm the event arrived.

Troubleshooting

Two chat bubbles

Cause: The widget is genuinely loading twice — for example the WordPress plugin plus a hand-pasted <script> tag, or a GTM tag without the safety check on its first line.

Solution: Remove one. On WordPress, keep the plugin.

The chat opens but every message fails

Cause: Usually YOUR_BOT_ID was never replaced with a real Bot ID. The chat window still appears — it falls back to defaults when it can't load your bot — so it looks installed right up until someone sends a message.

Solution: Copy the Bot ID from your dashboard's Deploy page and paste it into the tag. Your browser console will show a QuackChat warning about failing to load bot info.

No chat bubble at all

Cause: The tag never fired, or the browser blocked the script.

Solution: Check in GTM Preview that the tag fired on this page, and that you published the container. Then open your browser console: a Content-Security-Policy error means the script was blocked (see below), and an ERR_BLOCKED_BY_CLIENT error — or a request that never appears in the Network tab at all — usually means an ad blocker.

Console shows a Content-Security-Policy error

Cause: Your site restricts what it's allowed to load, and the widget isn't on the list.

Solution: Allowing the script alone isn't enough — the chat would load and then sit there broken. Add https://www.quackchat.app to each of these:

script-src  https://www.quackchat.app;   the widget itself
connect-src https://www.quackchat.app;   sending and receiving messages
font-src    https://www.quackchat.app;   the chat's typeface
img-src     https://www.quackchat.app;   your bot's avatar
media-src   https://www.quackchat.app;   the notification sound

Your style-src must also permit inline styles ('unsafe-inline') — the widget styles itself from inside its own container, and blocking that gives you a fully working chat with no styling at all. If you show product cards, your store's own domain needs to be in img-src too. You'll also need to permit Google Tag Manager's own tags, which your GTM setup guide will cover.

No QuackChat events in Preview

Cause: Most often the integration isn't connected — booking events need a practice management connection, cart events need a connected store, and both need a Pro plan.

Solution: Check Settings → Integrations in your bot's workspace shows an active connection, then try the action again in a fresh Preview session.

Events show in Preview but GA4 is empty

Cause: The trigger exists but no GA4 tag is attached to it, or a consent setting is holding the tag back.

Solution: Confirm a GA4 Event tag lists your Custom Event trigger, and check Consent Settings on that tag.

quackchat_value is empty

Cause: Your store's currency hasn't synced yet, so we withhold the amount rather than send a number with no currency — GA4 discards an amount that has no currency anyway. It's also empty for a product with no price.

Solution: Press Sync on the store connection (Settings → Integrations), or wait for the next automatic sync. Testing the connection isn't enough — only a sync saves the currency.

Nothing works for some visitors

Cause: Ad blockers block Google Tag Manager itself. If the widget is installed through GTM, those visitors get no chat.

Solution: Install the widget with the WordPress plugin or a script tag, and keep GTM for the analytics only.

Custom HTML isn't available

Cause: You're in a server-side GTM container, which can't run page scripts.

Solution: Add the tag in your web container instead.

Known Limitations

  • The events can't be switched off. They're part of the widget. There's no setting, and turning them off means removing the widget.
  • Anything on the page can read them. They're published to the page's data layer, which is shared by every script on your site. They carry no personal details, which is why this is safe — but it isn't private.
  • Purchases aren't tracked. We report the add-to-cart, not the completed order — following a customer through checkout would need tracking we deliberately don't do.
  • Bookings made outside the chat aren't counted. Only what happens in the chat is reported.

Stop letting warm leads go cold

Start converting while they're still on your site