Home / Guides

Tracking inside an embedded widget

A booking widget, a payment step or a chat box in an iframe has its own dataLayer. Why Preview shows one event where the log shows two, and what actually crosses the boundary.

· By hsynkvlc

An iframe is a separate document. It has its own window, its own dataLayer, possibly its own GTM container, and no obligation to tell the parent page anything. Most tracking confusion around booking widgets, payment steps and chat boxes comes from treating that boundary as if it were not there.

What GTM Preview does at the boundary

Preview attaches to the frame you opened it on. Events in another frame are either invisible or appear once when they happened twice. That is not a bug in Preview; it is the scope it was given. But it does mean "Preview showed one page_view" is not evidence that only one was sent.

Same-origin and cross-origin are different problems

The three failures worth knowing

  1. The conversion happens in someone else's property. A booking widget on the vendor's domain fires the vendor's own GA4. The event exists — just not in your account. What you get is a session that stops at the widget and no purchase, which reads like a tracking bug and is actually an architecture one.
  2. The payment step is a cross-domain hop wearing a costume. Whether it is an iframe or a redirect, leaving your origin without the linker starts a new client ID. The visitor comes back looking like a new person referred by the payment provider.
  3. The same container in both frames. Install GTM in the parent and again inside a same-origin iframe and you get two page_view events for one page. Sessions and engagement inflate quietly, and Preview — watching one frame — shows you a single, entirely healthy event.

Tag Master listens in every frame, not just the top one. Each dataLayer push is labelled with the frame it came from and the iframe's hostname, and a "Main frame" filter lets you hide or show them — which is how the duplicate case becomes obvious instead of invisible.

Checking it

  1. Load the page with the widget and count page_view events with the frame filter off. More than one means the container is installed twice.
  2. Interact with the widget. If nothing appears in any frame, the widget is cross-origin and silent, and the answer is a conversation with the vendor rather than a trigger.
  3. If events do appear from the iframe, check which account they carry — tid for GA4, the pixel ID for Meta. A vendor's own ID means the data is going to the vendor.
  4. Across the widget boundary, check cid the way you would for any domain hop.

If the vendor gives you postMessage

Then the pattern is: the widget posts an event, a listener in the parent receives it and pushes to the parent's dataLayer, and your own tags fire from there. Two things to insist on. Check event.origin against the expected host — a listener that accepts messages from anywhere will happily accept a purchase event from a page you did not write. And make sure the payload carries an order identifier, so the event can be deduplicated against whatever the vendor also sends.

Related guides

Try it on your own site

Tag Master is free, needs no account, and collects no data.

Add to Chrome — Free