Track iFrame form submissions with Google Tag Manager (2026)
Tracking user interactions is the backbone of any serious analytics setup, but some interactions are genuinely hard to capture. One of the trickiest is a form that lives inside an iframe you do not control, hosted on a third-party domain. The standard Google Tag Manager triggers cannot see it, and without a workaround the conversion simply goes untracked.
This is the exact problem I solved for a client whose booking form was embedded from a third-party provider. This guide, updated for 2026, walks through why the usual triggers fail, how to capture the submission through postMessage, and how to wire it into GA4 safely. Full code included.
Table of contents
- Why standard GTM form triggers fail with an iframe
- The approach: listening for postMessage
- Step 1: find the postMessage the iframe sends
- Step 2: create the Custom HTML listener tag
- Step 3: create the Custom Event trigger
- Step 4: fire your GA4 and conversion tags
- Security: always validate the origin
- Consent and privacy in 2026
- Testing and verification
- Troubleshooting
- FAQ
Why standard GTM form triggers fail with an iframe
GTM’s built-in Form Submission and Element Visibility triggers rely on reading the page’s DOM. When a form is embedded from another domain through an iframe, the browser’s same-origin policy blocks the parent page from reading anything inside that iframe. It is a security feature, not a bug: the parent site cannot inspect the fields, listen to the submit event, or see when an element appears.
That is why the “Element Visibility” trigger returns nothing for iframe content. The sanctioned way for a cross-origin iframe to talk to its parent is window.postMessage(), and that is what we will listen for.
The approach: listening for postMessage
Many embedded widgets, including booking and payment forms, send a postMessage to the parent window when something important happens, such as a successful submission. If the iframe you are dealing with does this, you can catch that message in GTM and turn it into your own dataLayer event.
In my case, the form sent a message with data.code === "success" on completion. That single signal is all we need.
Step 1: find the postMessage the iframe sends
Before writing anything in GTM, confirm what the iframe actually emits:
- Open the page with the embedded form and open Chrome DevTools (
Ctrl+Shift+IorCmd+Option+I). - Go to the Console tab.
- Paste this listener so every incoming message is logged:
1
2
3
window.addEventListener('message', function (event) {
console.log('origin:', event.origin, 'data:', event.data);
});- Submit the form inside the iframe and watch the console. Note two things: the exact
event.origin(protocol and domain) and the structure ofevent.data. You will need both.
In my case the message came from https://converto.simplebooking.it with event.data.code === "success".
Step 2: create the Custom HTML listener tag
In GTM, create a Custom HTML tag that listens for that message and pushes a clean event to the dataLayer:
1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
window.addEventListener('message', function (event) {
// Only trust messages from the known iframe origin.
if (event.origin !== 'https://converto.simplebooking.it') {
return;
}
if (event.data && event.data.code === 'success') {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ event: 'simplebooking_success' });
}
});
</script>Set its trigger to All Pages (or, better, only the pages where the iframe loads) and give it a high firing priority so it is listening before the user can submit.
Step 3: create the Custom Event trigger
Now react to the dataLayer event you just pushed:
- Trigger type: Custom Event
- Event name:
simplebooking_success - This trigger fires on: All Custom Events
Step 4: fire your GA4 and conversion tags
With the trigger in place, attach your tags to it. In 2026 the primary one is usually a GA4 Event tag:
- Tag type: Google Analytics: GA4 Event
- Event name:
generate_lead(or a name that matches your GA4 setup) - Trigger: the
simplebooking_successcustom event trigger
Mark that GA4 event as a key event (conversion) in GA4, and attach any other conversion tags (Google Ads, Meta) to the same trigger.
Security: always validate the origin
The most important line in the listener is the origin check. A message event can be sent by any window or script, so a listener that pushes an event without checking event.origin can be triggered by anyone. Always compare event.origin to the exact expected value (protocol and domain, no trailing slash), and only then read event.data. Treat event.data as untrusted input: check that the property you expect actually exists before using it.
Consent and privacy in 2026
Because this event feeds conversion and analytics tags, it must respect the user’s consent choices. If you use Google Consent Mode, make sure the GA4 and advertising tags attached to this trigger are gated by the relevant consent signals, so nothing fires before consent is granted where that is required. The listener itself only reacts to a form the user actively submitted, but the tags downstream still need to follow your consent setup.
Testing and verification
- GTM Preview / Tag Assistant: submit the form in the iframe and confirm the
simplebooking_successevent appears and your tags fire. - dataLayer check: in the console, run
window.dataLayerafter submitting to confirm the event was pushed. - GA4 DebugView: confirm the event and its parameters arrive in GA4.
Troubleshooting
- Nothing fires. The origin check is almost always the cause. Log
event.originagain and match it character for character, includinghttps://and no trailing slash. - The message has a different shape. Some widgets send
event.dataas a JSON string rather than an object. In that case parse it first:var data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;inside atry/catch. - Multiple messages arrive. Filter strictly on both origin and the specific property so unrelated messages are ignored.
- The Custom HTML tag is blocked. A strict Content Security Policy on the parent site can block inline GTM scripts. In that case use a GTM custom template or move the logic server-side (server-side GTM).
- The iframe sends no message at all. With a cross-origin iframe there is no reliable way to read its content. Ask the provider whether they emit a
postMessageor offer a callback or webhook.
Frequently asked questions
Why can't GTM's built-in form trigger track a form inside an iframe?
The browser's same-origin policy prevents the parent page from reading the content of a cross-origin iframe, so DOM-based triggers like Form Submission and Element Visibility cannot see it.
Is it safe to listen to postMessage events?
Yes, as long as you validate event.origin against the exact expected domain and treat event.data as untrusted before using it.
How do I find out what postMessage an iframe sends?
Add a message event listener in the browser console, submit the form, and inspect the logged origin and data.
Can I use this with GA4?
Yes. Push a custom event to the dataLayer, create a Custom Event trigger for it, and fire a GA4 Event tag on that trigger.
What if the iframe doesn't send any postMessage?
There is no reliable cross-origin way to detect the submission. Ask the third-party provider whether they expose a postMessage, callback or webhook.
Conclusion
When a conversion happens inside an iframe you do not control, postMessage is the bridge. Find the message the iframe emits, listen for it in a Custom HTML tag, validate the origin, push a clean dataLayer event, and fire your GA4 and conversion tags on it. With this pattern you can capture conversions that would otherwise be invisible, and keep your analytics honest.