How to Track Cliniko Bookings from Embedded Iframes in GA4 and Google Ads
If you're running paid ads to your healthcare practice website and using Cliniko's embedded booking widget, you've probably hit this wall: you can see patients are booking appointments, but you can't track those conversions back to your ad campaigns.
You ask Cliniko. They tell you tracking only works if you send traffic directly to your external booking page - the one that lives on yourclinicname.cliniko.com.
You ask your agency. They shrug and say "iframe tracking isn't possible" or suggest some convoluted workaround involving thank you pages that don't really exist.
So you're left with two options: accept that you can't measure your marketing ROI, or force patients off your website onto a subdomain that doesn't match your branding.
Neither is acceptable. And neither is necessary.
Because here's what we discovered: you absolutely can track Cliniko bookings from embedded iframes. Cliniko just doesn't know (or document) that their own platform makes it possible.
Why This Matters for Your Practice
Let's be clear about what's at stake here.
You're spending money on Google Ads or Meta campaigns. Patients are clicking through to your website, browsing your services, and booking appointments through your embedded Cliniko widget. That's the dream scenario.
But without conversion tracking, you have no idea which campaigns, keywords, or ads are actually driving those bookings. You're optimising blind. You might be pouring budget into campaigns that look good on clicks but deliver nothing, while starving the ones that actually bring patients through the door.
The standard advice - "just send traffic to the subdomain" - creates its own problems:
Patients land on a page that doesn't look like your website
You lose the trust and branding you've built
The user experience feels disjointed
You can't use your website's content to warm up the patient before they book
The embedded widget exists for good reasons. You shouldn't have to sacrifice tracking to use it.
The Technical Background
Before I explain what we found, it helps to understand why everyone assumes iframe tracking is impossible.
When you embed Cliniko's booking widget on your website, it loads inside an iframe - essentially a window into another website (cliniko.com) displayed within your page. Browsers enforce strict security rules around iframes to prevent malicious sites from stealing data. Your website's tracking code (GTM, GA4, Meta Pixel) runs on your domain. It genuinely cannot "see inside" the Cliniko iframe because that's a completely different origin.
This is where most people stop. The iframe is a black box. End of story.
But there's a mechanism built into browsers specifically for iframes to communicate with their parent pages: the postMessage API. It allows an iframe to send messages to the page it's embedded on, in a controlled and secure way. The parent page just needs to be listening.
Here's the thing: many SaaS platforms use postMessage to communicate events - form submissions, booking completions, errors. They do this for their own internal purposes. They just don't advertise it or document it for external use.
Which brings us to Cliniko.
What is postMessage?
If you're not familiar with postMessage, here's the simple explanation.
Browsers have strict security rules called the "same-origin policy." A script running on yourwebsite.com cannot access content from cliniko.com - and for good reason. Without this protection, malicious websites could steal your banking details from other tabs.
But sometimes iframes legitimately need to talk to their parent page. Maybe the iframe needs to tell the parent "I've resized" or "the user completed an action." The postMessage API was created for exactly this purpose.
It works like this:
The iframe sends a message using
window.parent.postMessage()The message includes some data and the iframe's origin (where it came from)
The parent page can listen for these messages using an event listener
The parent page can check the origin to make sure the message came from a trusted source
It's a secure, intentional communication channel. The iframe chooses what to share, and the parent page chooses what to listen for.
The key insight is this: many SaaS platforms send postMessage events as part of their normal functionality. They might use it to resize the iframe dynamically, communicate loading states, or confirm when actions complete. They're not doing this for your benefit - it's just how their widget works internally.
But if you're listening, you can catch these messages and use them for tracking.
How to Find postMessage Events
This is the investigative part. Before you can track anything, you need to see what messages (if any) are being sent.
Here's how to check:
Step 1: Open your website with the embedded Cliniko widget
Navigate to the page where you've embedded the booking form.
Step 2: Open your browser's developer console
Right-click anywhere on the page and select "Inspect" (or press F12). Click on the "Console" tab.
Step 3: Add a postMessage listener
Paste this code into the console and press Enter:
window.addEventListener('message', function(event) {
console.log('--- postMessage received ---');
console.log('Origin:', event.origin);
console.log('Data:', event.data);
});
This tells your browser to log every postMessage event that hits the page, showing you where it came from (origin) and what data it contains.
Step 4: Complete a test booking
Now go through the full booking flow in the Cliniko widget. Select a service, choose a time slot, enter your details, and confirm the booking.
Step 5: Watch the console
As you interact with the widget, you should see messages appearing in the console. Some will be noise - things like reCAPTCHA initialising or the iframe reporting its height. What you're looking for is something that fires specifically when the booking completes.
What postMessage Events Look Like
When you run this test, you'll see output in your console that looks something like this:
--- postMessage received ---
Origin: https://example-widget.com
Data: {"type":"resize","height":450}
--- postMessage received ---
Origin: https://example-widget.com
Data: {"type":"step","current":"details"}
--- postMessage received ---
Origin: https://example-widget.com
Data: {"type":"booking_complete","status":"confirmed"}
The exact format varies between platforms. Some send simple strings, others send JSON objects. Some are very descriptive, others are cryptic.
What you're looking for:
The origin - This tells you which domain sent the message. For Cliniko, you'd expect to see something containing "cliniko.com"
Completion indicators - Words like "complete," "confirmed," "success," "booked," or "finished" in the data
Timing - The message should appear right after you confirm the booking, not during the earlier steps
You might also see a lot of irrelevant messages - iframe resizing, scroll events, loading states. That's normal. You're hunting for the one that indicates a completed booking.
Once you've identified the specific message that fires on booking completion, you know what to listen for in your GTM setup.
What We Discovered
Working on a client project, we needed to track Cliniko bookings from an embedded widget. The client didn't want to redirect patients to an external subdomain. The agency they'd been working with had told them it wasn't possible.
We don't accept "not possible" without investigating first.
Using browser developer tools, we set up a listener to monitor any postMessage events firing on the page. Then we completed a test booking through the embedded widget.
And there it was.
Cliniko's iframe does broadcast events when certain actions complete. The messages include identifiable information about what happened - enough to distinguish a completed booking from other activity.
This meant we could build a Google Tag Manager setup that:
Listens for postMessage events from the Cliniko origin
Identifies when a booking has been completed
Pushes that event to the dataLayer
Fires conversion tracking for GA4, Google Ads, Meta - whatever the client needs
The client now has full visibility on which campaigns drive actual appointments. They can optimise their ad spend based on real data, not guesswork.
The General Approach
I'm not going to share the exact implementation code here. There are good reasons for that, which I'll explain in a moment. But I will walk through the methodology so you understand what's involved.
Step 1: Set up a postMessage listener in your browser
Before building anything in GTM, you need to see what events Cliniko actually sends. Open your browser's developer console on the page with the embedded widget and add a listener that logs all incoming messages. This is standard JavaScript that any developer can write.
Step 2: Complete a test booking
Go through the full booking flow on your own website - select a service, pick a time, enter details, confirm. Watch what appears in your console.
Step 3: Identify the patterns
You're looking for messages that originate from Cliniko's domain and contain indicators of a completed booking. Note the structure of the data being sent.
Step 4: Build the GTM listener
Create a Custom HTML tag in GTM that listens for these specific postMessage events. When it detects a booking completion, it pushes a custom event to the dataLayer.
Step 5: Set up your conversion triggers
Create a trigger based on your custom dataLayer event, then attach it to your GA4, Google Ads, or Meta conversion tags. Now when a booking completes in the iframe, your conversion pixels fire.
Step 6: Test thoroughly
Use GTM's preview mode and your ad platforms' testing tools to confirm everything fires correctly. Check that conversions appear in your dashboards.
Why I'm Not Sharing the Exact Code
This might seem counterintuitive for a blog post about solving a problem. But there are genuine reasons I'm keeping the specific implementation private.
Every setup has nuances. Your GTM container, consent mode configuration, existing tags, and site structure all affect how this needs to be built. Copy-pasting code without understanding the context leads to problems.
Getting it wrong has consequences. A poorly implemented listener could fire false conversions (inflating your numbers and ruining your data) or miss real ones (defeating the purpose entirely). It could conflict with your consent management platform, creating compliance issues.
The listener needs proper safeguards. You need origin validation to ensure you're only catching messages from Cliniko, not random postMessage spam. You need error handling. You need to account for edge cases like partial bookings or timeouts.
This is specialist work. Not because the concept is complicated, but because the implementation needs to be bulletproof. You're making business decisions based on this data. It needs to be right.
If you have a competent developer or GTM specialist who understands postMessage and dataLayer mechanics, they can absolutely figure this out using the methodology above. If you don't, this is exactly the kind of work you should hire someone for.
The Bigger Picture
This isn't really a blog post about Cliniko. It's about a mindset.
When a vendor says something isn't possible, that's not always the final answer. When an agency says they can't track something, it might just mean they haven't tried hard enough.
The same approach we used for Cliniko has worked for other embedded booking systems, form providers, and third-party widgets. The postMessage API is widely used. The question is whether anyone has bothered to listen.
I've lost count of the number of times we've solved "impossible" tracking problems by simply opening the browser console and seeing what's actually happening on the page. The data is often there. It's just not documented.
If you're working with any embedded system and you've been told conversion tracking isn't possible, it's worth investigating. You might be surprised.
Need Help With This?
If you're running ads to a healthcare practice website with an embedded Cliniko booking widget and you're not tracking conversions, this is fixable.
We've implemented this for physiotherapy clinics, psychology practices, dental surgeries, and allied health providers. The setup is reliable, compliant with consent requirements, and gives you the conversion data you need to actually optimise your campaigns.
Get in touch if you want to stop flying blind on your healthcare marketing.