{"id":91327,"date":"2025-02-16T08:44:07","date_gmt":"2025-02-16T08:44:07","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/cookieless-tracking-for-cross-site-iframes\/"},"modified":"2025-02-16T08:44:07","modified_gmt":"2025-02-16T08:44:07","slug":"cookieless-tracking-for-cross-site-iframes","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=91327","title":{"rendered":"Cookieless Tracking For Cross-site Iframes"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<blockquote>\n<p><strong>Updated 25 May 2021<\/strong>: Added information about using this with GA4. As Google Analytics 4 does not have a mechanism to disable cookie storage, only the second solution (send <code>dataLayer<\/code> events from iframe to the parent) described in this article will work for GA4.<\/p>\n<\/blockquote>\n<p>Here I am, back with <code><iframe><\/iframe><\/code> and <strong>cross-domain tracking<\/strong>. I\u2019ve published a couple of articles before on the topic, with <a href=\"https:\/\/www.simoahava.com\/analytics\/tracking-cross-domain-iframes-upgraded-solution\/\">my upgraded solution<\/a> being the most recent one. These articles tackle the general problem of <strong>passing the Client ID from the parent to the <code><iframe><\/iframe><\/code><\/strong>.<\/p>\n<p>By doing so, the <code><iframe><\/iframe><\/code> can take the Client ID from the frame URL and create the <code>_ga<\/code> cookie in the <code><iframe><\/iframe><\/code>, allowing hits from the parent <em>and<\/em> the <code><iframe><\/iframe><\/code> to use the same Client ID. Great.<\/p>\n<p>However, there\u2019s a catch that\u2019s become more and more troubling with the increase of <a href=\"https:\/\/www.cookiestatus.com\">browsers\u2019 tracking protection mechanisms<\/a>: if the <code><iframe><\/iframe><\/code> loads content <strong>cross-site<\/strong>, the browser must <strong>allow cookie access in third-party context<\/strong>. In other words, if the browser blocks <em>third-party cookies<\/em>, the <code><iframe><\/iframe><\/code> will not be able to write the cookie, and Google Analytics tracking <strong>will fail<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1396 \/ 472;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/02\/iframe-postmessage.jpg\" title=\"Iframe post message\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"472\" width=\"1396\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/02\/iframe-postmessage.jpg#ZgotmplZ\" alt=\"Iframe post message\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This article offers <strong>two<\/strong> solutions to this problem.<\/p>\n<p>The <strong>first<\/strong> is that it sends the Google Analytics Client ID from the parent to the <code><iframe><\/iframe><\/code> using <code>window.postMessage<\/code>, and the child frame can poll for this information on every page, meaning cookies are not needed to store the Client ID.<\/p>\n<p>The <strong>second<\/strong> solution is that the child frame actually sends every single <code>dataLayer<\/code> message to the parent, so that the child frame doesn\u2019t have to track anything by itself. The parent handles the tracking for the child frame instead.<\/p>\n<p>Using <code>window.postMessage<\/code> is more robust than the link decoration of my earlier proposals, because it doesn\u2019t force a reload of the frame, nor does it require the child frame to support cookies. It\u2019s a bit more elaborate to set up, however.<\/p>\n<blockquote>\n<p>Warning! I\u2019m not kidding about that last statement. Setting this up requires quite a bit of custom code, and you\u2019re working with bilateral communication between two windows. Please read this article carefully so that you understand what\u2019s going on before copy-pasting any code to your Google Tag Manager container(s).<\/p>\n<\/blockquote>\n<p>                <span class=\"simmer\"><br \/>\n  <span class=\"close\">X<\/span><\/p>\n<p>\n    <span class=\"fa fa-md fa-bell\"\/><br \/>\n    <strong>The Simmer Newsletter<\/strong>\n  <\/p>\n<p>\n    Subscribe to the <a href=\"https:\/\/www.simoahava.com\/newsletter\/\">Simmer newsletter<\/a> to get the latest news and content from Simo Ahava into your email inbox!\n  <\/p>\n<p>  <\/span><\/p>\n<h2 id=\"what-exactly-is-the-problem\">What exactly is the problem?<\/h2>\n<p>Glad you asked!<\/p>\n<p>When a page loads an <code><iframe><\/iframe><\/code> from a <strong>cross-site<\/strong> origin, that frame is loaded in a third-party context, and any access to browser storage from within that <code><iframe><\/iframe><\/code> will require the browser to allow third-party cookies for the <code><iframe> site.<\/iframe><\/code><\/p>\n<p>A key term here is <strong>cross-site<\/strong>. This means that the <strong>top privately-controlled domain name<\/strong> of the <code><iframe><\/iframe><\/code> is different from that of the parent page.<\/p>\n<p>In the examples below, the top privately-controlled domain name is in italics.<\/p>\n<ul>\n<li>www.<em>simoahava.com<\/em><\/li>\n<li>www.<em>gtmtools.com<\/em><\/li>\n<li>www.<em>ebay.co.uk<\/em><\/li>\n<li><em>sahava.github.io<\/em><\/li>\n<li>analytics.<em>google.com<\/em><\/li>\n<\/ul>\n<p>If any one of the sites above loaded any other site from the list in an <code><iframe><\/iframe><\/code>, that content would be loaded cross-site, and any cookie operations within the embedded page would require third-party cookie access.<\/p>\n<p>The following examples are all <strong>same-site<\/strong>, even if they are cross-origin:<\/p>\n<ul>\n<li>www.<em>simoahava.com<\/em><\/li>\n<li><em>simoahava.com<\/em><\/li>\n<li>blog.<em>simoahava.com<\/em><\/li>\n<li>tracking.endpoint.<em>simoahava.com<\/em><\/li>\n<\/ul>\n<p>Any communication between pages from the list above would happen in a <strong>same-site<\/strong> (or first-party) context, and cookie access would not be restricted.<\/p>\n<p>As I mention in the introduction, this is only going to spell doom for tracking within embedded content due to how browsers implement tracking protections.<\/p>\n<div style=\"aspect-ratio: 2878 \/ 1212;\" class=\"figure \">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/02\/tracking-protections.jpg\" title=\"From www.cookiestatus.com\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1212\" width=\"2878\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/02\/tracking-protections.jpg#ZgotmplZ\" alt=\"From www.cookiestatus.com\"\/><\/p>\n<p>    <\/a><\/p>\n<p>    <span class=\"caption\">From www.cookiestatus.com<\/span><\/p>\n<\/div>\n<p>Even though Google recently made a veritable non-announcement by saying they\u2019ll <a href=\"https:\/\/blog.chromium.org\/2020\/01\/building-more-private-web-path-towards.html\">phase out third-party cookies by 2022<\/a>, Google Chrome will actually make things harder for cross-site cookie access much, much sooner.<\/p>\n<p>Chrome v80 (released on February 4, 2020), enforces <a href=\"https:\/\/web.dev\/samesite-cookies-explained\/\">SameSite cookie restrictions<\/a>, which means that if a cookie should be accessible in third-party context, it requires the <code>SameSite=None<\/code> and <code>Secure<\/code> flags set. By default, these are <em>not<\/em> set.<\/p>\n<blockquote>\n<p>You <strong>can<\/strong> set those flags with the <a href=\"https:\/\/www.simoahava.com\/analytics\/cookieflags-field-google-analytics\/\"><code>cookieFlags<\/code><\/a> setting, but do note that this only fixes the \u201cproblem\u201d for Google Chrome. It doesn\u2019t make those cookies work in browsers that block third-party cookies.<\/p>\n<\/blockquote>\n<p>Luckily, there\u2019s a way around this.<\/p>\n<p>We can ignore cookies altogether.<\/p>\n<h2 id=\"solution-1-pass-the-client-id-from-the-parent-to-the-child\">Solution 1: Pass the Client ID from the parent to the child<\/h2>\n<blockquote>\n<p><strong>NOTE!<\/strong> As there is currently no mechanism to disable cookie storage from Google Analytics 4 deployments, this solution will only work for Universal Analytics. For GA4, you need to use <a href=\"#solution-2-forward-all-data-layer-messages-from-child-to-parent\">Solution 2<\/a> below.<\/p>\n<\/blockquote>\n<p>Let\u2019s take a look at another celebrated illustration from yours truly:<\/p>\n<div style=\"aspect-ratio: 2614 \/ 1612;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/02\/postmessage-process.jpg\" title=\"postMessage process\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1612\" width=\"2614\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/02\/postmessage-process.jpg#ZgotmplZ\" alt=\"postMessage process\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>There are many potential <strong>race conditions<\/strong> in the mix, so some precautions need to be taken. Here\u2019s how the parent page works:<\/p>\n<ol>\n<li>The parent page starts listening for messages from the <code><iframe><\/iframe><\/code> as soon as Google Tag Manager loads.<\/li>\n<li>Once the parent page receives the <code>childReady<\/code> message, it starts polling for the Google Analytics tracker until a maximum timeout is reached.<\/li>\n<li>As soon as the Google Analytics tracker is available, the parent page sends the Client ID back to the <code><iframe><\/iframe><\/code>.<\/li>\n<\/ol>\n<p>And here\u2019s how the <code><iframe><\/iframe><\/code> page reacts:<\/p>\n<ol>\n<li>The <code><iframe><\/iframe><\/code> page starts sending the <code>childReady<\/code> message as soon as Google Tag Manager loads.<\/li>\n<li>Once the parent page responds with the Client ID (or a timeout is reached), the child page stops sending the message.<\/li>\n<li>The child page writes the Client ID into <code>dataLayer<\/code>.<\/li>\n<\/ol>\n<p>So now we know how the Client ID will be passed to the <code><iframe><\/iframe><\/code>, but that\u2019s not enough, yet.<\/p>\n<p><strong>Every single Google Analytics tag in the <code><iframe><\/iframe><\/code> must be configured to work with this setup!<\/strong><\/p>\n<p>More specifically, they all need two <strong>fields<\/strong> set, preferably in a <a href=\"https:\/\/www.simoahava.com\/analytics\/google-analytics-settings-variable-in-gtm\/\">Google Analytics Settings variable<\/a>:<\/p>\n<ul>\n<li><code>storage<\/code> set to <code>none<\/code> to avoid the tracker failing if it can\u2019t write the Client ID cookie.<\/li>\n<li><code>clientId<\/code> set to the value from the <code>dataLayer<\/code>.<\/li>\n<\/ul>\n<p>We\u2019ll get to these <a href=\"#google-analytics-tags\">shortly<\/a>, don\u2019t worry.<\/p>\n<h2 id=\"solution-2-forward-all-data-layer-messages-from-child-to-parent\">Solution 2: Forward all Data Layer messages from child to parent<\/h2>\n<p>If you don\u2019t want to do any tracking within the <code><iframe><\/iframe><\/code> (I don\u2019t blame you), you can actually delegate tracking to the parent by sending all <code>dataLayer<\/code> messages to the parent for processing.<\/p>\n<blockquote>\n<p>This is the only viable solution for tracking cross-site iframe traffic with GA4.<\/p>\n<\/blockquote>\n<p>This means the parent page would manage tags for both the parent page\u2019s native interactions <em>as well as<\/em> those that happen within the <code><iframe><\/iframe><\/code>.<\/p>\n<div style=\"aspect-ratio: 3054 \/ 1932;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/02\/datalayer-listener.jpg\" title=\"dataLayer message forwarder\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1932\" width=\"3054\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/02\/datalayer-listener.jpg#ZgotmplZ\" alt=\"dataLayer message forwarder\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The process is almost the same as with the first solution. Here\u2019s how the parent page works:<\/p>\n<ol>\n<li>The parent page starts listening for messages from the <code><iframe><\/iframe><\/code> as soon as Google Tag Manager loads.<\/li>\n<li>Once the parent page receives the <code>childReady<\/code> message, it responds with a <code>parentReady<\/code> message.<\/li>\n<li>If the child frame sends a message in <code>dataLayer<\/code>-compatible format, the parent page pushes this message into its own <code>dataLayer<\/code>.<\/li>\n<\/ol>\n<p>On the <code><iframe><\/iframe><\/code>, this is what happens:<\/p>\n<ol>\n<li>The <code><iframe><\/iframe><\/code> page starts sending the <code>childReady<\/code> message as soon as Google Tag Manager loads.<\/li>\n<li>Once the parent page responds with <code>parentReady<\/code>, the child frame \u201chijacks\u201d the <code>dataLayer.push()<\/code> method, and sends all the messages passed to it over to the parent page.<\/li>\n<\/ol>\n<p>The messages from the child frame are namespaced to keep them separate from the parent\u2019s \u201cnative\u201d <code>dataLayer<\/code> messages, and they include some metadata about the <code><iframe><\/iframe><\/code> (mainly the URL and title).<\/p>\n<div style=\"aspect-ratio: 1278 \/ 894;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/02\/parent-datalayer.jpg\" title=\"Parent dataLayer\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"894\" width=\"1278\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/02\/parent-datalayer.jpg#ZgotmplZ\" alt=\"Parent dataLayer\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h2 id=\"the-parent-page-setup\">The parent page setup<\/h2>\n<p>This article combines <strong>both<\/strong> solutions into a single set of Custom HTML tags, one for the parent page and one for the child <code><iframe><\/iframe><\/code>.<\/p>\n<p>On the parent page, i.e. the page sending the Client ID to the <code><iframe><\/iframe><\/code> and waiting for the messages sent from the child, you need to create a <strong>Custom HTML tag<\/strong> that fires on a <strong>Page View<\/strong> trigger. You can use the <em>All Pages<\/em> trigger if you wish, but you might as well create a <strong>Page View<\/strong> trigger that only fires on pages where you know the <code><iframe><\/iframe><\/code> to exist.<\/p>\n<h3 id=\"the-custom-html-tag\">The Custom HTML tag<\/h3>\n<p>The Custom HTML tag itself should contain the following code:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-html\" data-lang=\"html\">script&gt;\n  (<span style=\"color:#00a\">function<\/span>() {\n    <span style=\"color:#aaa;font-style:italic\">\/\/ Universal Analytics tracking ID whose _ga cookie to use.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#aaa;font-style:italic\">\/\/ If using GA4, you can leave this setting untouched.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> trackingId = <span style=\"color:#a50\">'UA-XXXXX-Y'<\/span>;\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Maximum time in milliseconds to wait for GA tracker to load.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#aaa;font-style:italic\">\/\/ Again, irrelevant for GA4.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> maxGATime = <span style=\"color:#099\">2000<\/span>;\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Set to the origin (\"https:\/\/www.domain.com\") of the iframe you want to communicate with\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> childOrigin = <span style=\"color:#a50\">'https:\/\/www.iframe-domain.com'<\/span>;\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Don't touch anything that follows\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> pollInterval = <span style=\"color:#099\">200<\/span>;\n    \n    <span style=\"color:#00a\">var<\/span> postCallback = <span style=\"color:#00a\">function<\/span>(event) {\n      <span style=\"color:#00a\">if<\/span> (event.origin !== childOrigin) <span style=\"color:#00a\">return<\/span>;\n      <span style=\"color:#00a\">if<\/span> (event.data !== <span style=\"color:#a50\">'childReady'<\/span> &amp;&amp; !event.data.event) <span style=\"color:#00a\">return<\/span>;\n      \n      <span style=\"color:#00a\">if<\/span> (event.data === <span style=\"color:#a50\">'childReady'<\/span>) {\n        <span style=\"color:#aaa;font-style:italic\">\/\/ Send event that parent is ready\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        event.source.postMessage(<span style=\"color:#a50\">'parentReady'<\/span>, event.origin);\n\n        <span style=\"color:#00a\">var<\/span> pollCallback = <span style=\"color:#00a\">function<\/span>() {\n          <span style=\"color:#aaa;font-style:italic\">\/\/ Stop polling if maxTime reached\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>          maxGATime -= pollInterval;\n          <span style=\"color:#00a\">if<\/span> (maxGATime 0) <span style=\"color:#0aa\">window<\/span>.clearInterval(poll);\n          \n          <span style=\"color:#aaa;font-style:italic\">\/\/ Only proceed if GA loaded and tracker accessible\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>          <span style=\"color:#00a\">var<\/span> ga = <span style=\"color:#0aa\">window<\/span>[<span style=\"color:#0aa\">window<\/span>[<span style=\"color:#a50\">'GoogleAnalyticsObject'<\/span>]];\n          <span style=\"color:#00a\">if<\/span> (ga &amp;&amp; ga.getAll) {\n            <span style=\"color:#aaa;font-style:italic\">\/\/ Get tracker that matches the Tracking ID you provided\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>            <span style=\"color:#00a\">var<\/span> tracker = ga.getAll().filter(<span style=\"color:#00a\">function<\/span>(t) { \n              <span style=\"color:#00a\">return<\/span> t.get(<span style=\"color:#a50\">'trackingId'<\/span>) === trackingId; \n            }).shift();\n            \n            <span style=\"color:#aaa;font-style:italic\">\/\/ Send message back to frame with Client ID\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>            <span style=\"color:#00a\">if<\/span> (tracker) {\n              event.source.postMessage({\n                event: <span style=\"color:#a50\">'clientId'<\/span>,\n                clientId: tracker.get(<span style=\"color:#a50\">'clientId'<\/span>)\n              }, event.origin);\n            }\n            <span style=\"color:#aaa;font-style:italic\">\/\/ Stop polling if not already done so\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>            <span style=\"color:#0aa\">window<\/span>.clearInterval(poll);\n          }\n        };\n        \n        <span style=\"color:#aaa;font-style:italic\">\/\/ Start polling for Google Analytics tracker\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        <span style=\"color:#00a\">var<\/span> poll = <span style=\"color:#0aa\">window<\/span>.setInterval(pollCallback, pollInterval)\n      }\n      \n      <span style=\"color:#aaa;font-style:italic\">\/\/ Push dataLayer message from iframe to dataLayer of parent\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>      <span style=\"color:#00a\">if<\/span> (event.data.event) {\n        <span style=\"color:#0aa\">window<\/span>.dataLayer.push(event.data);\n      }\n    };\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Start listening for messages from child frame\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#0aa\">window<\/span>.addEventListener(<span style=\"color:#a50\">'message'<\/span>, postCallback);\n  })();\n<span style=\"color:#1e90ff;font-weight:bold\">script<\/span>&gt;<\/code><\/pre>\n<\/div>\n<p>There\u2019s quite a lot happening here, so let\u2019s walk through the code! If you don\u2019t care about this deep-dive, you can <a href=\"#configuring-the-message-forwarding-system\">skip right to<\/a> how you might need to configure the parent page container to support the message forwarding setup.<\/p>\n<h4 id=\"configuration\">Configuration<\/h4>\n<p>First, there\u2019s the configuration stuff:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#aaa;font-style:italic\">\/\/ Tracking ID whose _ga cookie to use\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">var<\/span> trackingId = <span style=\"color:#a50\">'UA-XXXXX-Y'<\/span>;\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Maximum time in milliseconds to wait for GA tracker to load\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">var<\/span> maxGATime = <span style=\"color:#099\">2000<\/span>;\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Set to the origin (\"https:\/\/www.domain.com\") of the iframe you want to communicate with\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">var<\/span> childOrigin = <span style=\"color:#a50\">'https:\/\/www.iframe-domain.com'<\/span>;\n<\/code><\/pre>\n<\/div>\n<p>The <code>trackingId<\/code> should be set to the Google Analytics tracking ID of the tracker whose <code>_ga<\/code> cookie you want to use. This is because there might be multiple GA cookies each storing the Client ID for a different Tracking ID. If you\u2019re unsure, just type your regular tracking ID as the value of the <code>trackingId<\/code> variable.<\/p>\n<p>Set <code>maxGATime<\/code> to the maximum amount of time that the page waits for Google Analytics to load. You can certainly set this a lot higher than <em>2000<\/em> (2 seconds) if you want, but I would recommend against indefinite polling.<\/p>\n<blockquote>\n<p>The <code>trackingId<\/code> and <code>maxGATime<\/code> settings are only relevant for Universal Analytics. If tracking to GA4, you do not need to change these from their default values.<\/p>\n<\/blockquote>\n<p>Set the <code>childOrigin<\/code> to the <strong>origin<\/strong> of the <code><iframe><\/iframe><\/code> you want to send the data to. The <strong>origin<\/strong> is everything in the URL up to the first <em>path<\/em> slash. So, if the URL is <code>https:\/\/www.simoahava.com\/my-home-page\/<\/code>, the <strong>origin<\/strong> would be <code>https:\/\/www.simoahava.com<\/code>.<\/p>\n<blockquote>\n<p>It\u2019s important to <strong>not<\/strong> have the trailing slash, as that\u2019s part of the <em>path<\/em> component and not the <em>origin<\/em>.<\/p>\n<\/blockquote>\n<h4 id=\"the-listener\">The listener<\/h4>\n<p>On the last line of the main code block, we add the <code>message<\/code> listener:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#0aa\">window<\/span>.addEventListener(<span style=\"color:#a50\">'message'<\/span>, postCallback);\n<\/code><\/pre>\n<\/div>\n<p>This means that when an <code><iframe><\/iframe><\/code> sends a <code>postMessage<\/code> to the parent page, the listener fires and executes the <code>postCallback<\/code> function.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">var<\/span> postCallback = <span style=\"color:#00a\">function<\/span>(event) {\n  <span style=\"color:#00a\">if<\/span> (event.origin !== childOrigin) <span style=\"color:#00a\">return<\/span>;\n  <span style=\"color:#00a\">if<\/span> (event.data !== <span style=\"color:#a50\">'childReady'<\/span> &amp;&amp; !event.data.event) <span style=\"color:#00a\">return<\/span>;\n\n  <span style=\"color:#00a\">if<\/span> (event.data === <span style=\"color:#a50\">'childReady'<\/span>) {\n    <span style=\"color:#aaa;font-style:italic\">\/\/ Send event that parent is ready\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    event.source.postMessage(<span style=\"color:#a50\">'parentReady'<\/span>, event.origin);\n\n    <span style=\"color:#00a\">var<\/span> pollCallback = <span style=\"color:#00a\">function<\/span>() {\n      <span style=\"color:#aaa;font-style:italic\">\/\/ Stop polling if maxTime reached\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>      maxGATime -= pollInterval;\n      <span style=\"color:#00a\">if<\/span> (maxGATime 0) <span style=\"color:#0aa\">window<\/span>.clearInterval(poll);\n\n      <span style=\"color:#aaa;font-style:italic\">\/\/ Only proceed if GA loaded and tracker accessible\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>      <span style=\"color:#00a\">var<\/span> ga = <span style=\"color:#0aa\">window<\/span>[<span style=\"color:#0aa\">window<\/span>[<span style=\"color:#a50\">'GoogleAnalyticsObject'<\/span>]];\n      <span style=\"color:#00a\">if<\/span> (ga &amp;&amp; ga.getAll) {\n        <span style=\"color:#aaa;font-style:italic\">\/\/ Get tracker that matches the Tracking ID you provided\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        <span style=\"color:#00a\">var<\/span> tracker = ga.getAll().filter(<span style=\"color:#00a\">function<\/span>(t) { \n          <span style=\"color:#00a\">return<\/span> t.get(<span style=\"color:#a50\">'trackingId'<\/span>) === trackingId; \n        }).shift();\n\n        <span style=\"color:#aaa;font-style:italic\">\/\/ Send message back to frame with Client ID\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        <span style=\"color:#00a\">if<\/span> (tracker) {\n          event.source.postMessage({\n            event: <span style=\"color:#a50\">'clientId'<\/span>,\n            clientId: tracker.get(<span style=\"color:#a50\">'clientId'<\/span>)\n          }, event.origin);\n        }\n        <span style=\"color:#aaa;font-style:italic\">\/\/ Stop polling if not already done so\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        <span style=\"color:#0aa\">window<\/span>.clearInterval(poll);\n      }\n    };\n\n    <span style=\"color:#aaa;font-style:italic\">\/\/ Start polling for Google Analytics tracker\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> poll = <span style=\"color:#0aa\">window<\/span>.setInterval(pollCallback, pollInterval)\n  }\n  \n  <span style=\"color:#aaa;font-style:italic\">\/\/ Push dataLayer message from iframe to dataLayer of parent\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#00a\">if<\/span> (event.data.event) {\n    <span style=\"color:#0aa\">window<\/span>.dataLayer.push(event.data);\n  }\n};\n<\/code><\/pre>\n<\/div>\n<p>First, if the message retrieved was not expected (e.g. coming from a different <code><iframe><\/iframe><\/code> or has the wrong content), the callback stops execution.<\/p>\n<p>If the message <em>was<\/em> from the <code><iframe><\/iframe><\/code>, the first thing that\u2019s done is notify the child frame that the parent has received the message and is ready to start bilateral communication.<\/p>\n<p>Next, a <code>window.setInterval<\/code> starts polling the parent page every 200 milliseconds up to the default of 2 full seconds. With each poll, the script checks if the Google Analytics tracker has been created. If it has, the script takes the <code>clientId<\/code> from the tracker, and sends it back to the <code><iframe><\/iframe><\/code> using <code>event.source.postMessage()<\/code>. After this is done, the polling is manually stopped to avoid having the Client ID being sent multiple times to the <code><iframe><\/iframe><\/code>.<\/p>\n<p>In essence, the parent page needs to wait for two things:<\/p>\n<ol>\n<li>For the child page to signal it is ready to receive messages.<\/li>\n<li>For the Google Analytics tracker to be available, so that the Client ID can be grabbed from it.<\/li>\n<\/ol>\n<p>The last code block checks if the message from the child frame contains an object with the <code>event<\/code> property, in which case it pushes this entire object into the parent page <code>dataLayer<\/code>.<\/p>\n<h3 id=\"configuring-the-message-forwarding-system\">Configuring the message forwarding system<\/h3>\n<p>The parent page listens for <code>dataLayer<\/code> messages forwarded from the embedded <code><iframe><\/iframe><\/code>. You can create tags, triggers, and variables that react to these messages.<\/p>\n<p>All triggers will be of type <strong>Custom Event trigger<\/strong>. That\u2019s because all the events sent from the frame will be appended with the <code>iframe.<\/code> prefix &#8211; even those without an <code>event<\/code> value (they\u2019ll be sent as <code>iframe.Message<\/code>). So, if you want to fire a tag when a link is clicked in the <code><iframe><\/iframe><\/code>, the trigger could look like this:<\/p>\n<div style=\"aspect-ratio: 2254 \/ 1092;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/02\/iframe-link-click.jpg\" title=\"Iframe link click\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1092\" width=\"2254\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/02\/iframe-link-click.jpg#ZgotmplZ\" alt=\"Iframe link click\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>It requires quite a bit of manual configuration to get the whole thing up and running. But it can make your whole setup run much smoother, as you won\u2019t need to embed any extra code within the <code><iframe><\/iframe><\/code>.<\/p>\n<p>Each message sent from the <code><iframe><\/iframe><\/code> is automatically enhanced with some page-level information:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">{\n  iframe: {\n    pageData: {\n      url: <span style=\"color:#a50\">'https:\/\/www.iframe-domain.com\/iframe-page?iframequery=iframevalue#iframeHash'<\/span>,\n      title: <span style=\"color:#a50\">'The Iframe Page Title | Iframe Company'<\/span>\n    }\n  }\n}\n<\/code><\/pre>\n<\/div>\n<p>If you want to update your parent page tags to use the <code><iframe><\/iframe><\/code> page-level data, you need to create Data Layer variables for <strong>iframe.pageData.url<\/strong> (the URL of the <code><iframe><\/iframe><\/code> page) and <strong>iframe.pageData.title<\/strong> (the page title).<\/p>\n<p>Once you have all these configured, you\u2019re ready to configure the <code><iframe><\/iframe><\/code> page!<\/p>\n<h2 id=\"the-embedded-child-page-setup\">The embedded (child) page setup<\/h2>\n<p>This is where things get tricky. In this guide, we\u2019re covering a fairly typical use case where the <code><iframe><\/iframe><\/code> content is only ever interacted with as an embed. So there\u2019s no scenario where the user would visit the page loaded in the <code><iframe><\/iframe><\/code> in a first-party or top-frame context. I\u2019ll <a href=\"#multi-purpose-container\">briefly discuss that scenario later<\/a> as well, but for now let\u2019s assume that the page in the <code><iframe><\/iframe><\/code> is only accessed as an embedded element.<\/p>\n<p>You\u2019ll need to do <strong>three<\/strong> things in the Google Tag Manager container of the <code><iframe><\/iframe><\/code> page.<\/p>\n<ol>\n<li>Create a Custom HTML tag that communicates with the parent page.<\/li>\n<li>Update the settings for <strong>all<\/strong> of your Universal Analytics (and App+Web while you\u2019re at it) tags.<\/li>\n<li>Update the triggers for your Universal Analytics tags so they don\u2019t fire until they\u2019ve received the Client ID from the parent.<\/li>\n<\/ol>\n<h3 id=\"the-custom-html-tag-1\">The Custom HTML tag<\/h3>\n<p>Create a new Custom HTML tag, and set it to fire on the <strong>All Pages<\/strong> trigger. If the <code><iframe><\/iframe><\/code> is a single-page app, you should still only fire the Custom HTML tag on the All Pages trigger, and not with every SPA page change, for example.<\/p>\n<p>Here\u2019s what you should copy-paste into the tag:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-html\" data-lang=\"html\">script&gt;\n  (<span style=\"color:#00a\">function<\/span>() {\n    <span style=\"color:#aaa;font-style:italic\">\/\/ If not in iframe, do nothing\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">try<\/span> {\n      <span style=\"color:#00a\">if<\/span> (<span style=\"color:#0aa\">window<\/span>.top === <span style=\"color:#0aa\">window<\/span>.self) <span style=\"color:#00a\">return<\/span>;\n    } <span style=\"color:#00a\">catch<\/span>(e) {}\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Set to false to prevent dataLayer messages from being sent to parent\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> sendDataLayerMessages = <span style=\"color:#00a\">true<\/span>;\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Set the prefix that will be used in the event name, and under which all\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#aaa;font-style:italic\">\/\/ the dataLayer properties will be embedded\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> dataLayerMessagePrefix = <span style=\"color:#a50\">'iframe'<\/span>;\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Set to parent origin (\"https:\/\/www.domain.com\")\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> parentOrigin = <span style=\"color:#a50\">'https:\/\/www.parent-domain.com'<\/span>;\n\n    <span style=\"color:#aaa;font-style:italic\">\/\/ Maximum time in milliseconds to poll the parent frame for ready signal\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> maxTime = <span style=\"color:#099\">2000<\/span>;\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Don't touch anything that follows\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> pollInterval = <span style=\"color:#099\">200<\/span>;\n    <span style=\"color:#00a\">var<\/span> parentReady = <span style=\"color:#00a\">false<\/span>;\n    \n    <span style=\"color:#00a\">var<\/span> postCallback = <span style=\"color:#00a\">function<\/span>(event) {\n      <span style=\"color:#00a\">if<\/span> (event.origin !== parentOrigin) <span style=\"color:#00a\">return<\/span>;\n      <span style=\"color:#00a\">if<\/span> (event.data.event !== <span style=\"color:#a50\">'clientId'<\/span> &amp;&amp; event.data !== <span style=\"color:#a50\">'parentReady'<\/span>) <span style=\"color:#00a\">return<\/span>;\n      \n      <span style=\"color:#00a\">if<\/span> (event.data.event === <span style=\"color:#a50\">'clientId'<\/span>) {\n        <span style=\"color:#0aa\">window<\/span>.dataLayer.push({\n          event: <span style=\"color:#a50\">'clientId'<\/span>,\n          clientId: event.data.clientId\n        });\n      }\n      \n      <span style=\"color:#00a\">if<\/span> (event.data === <span style=\"color:#a50\">'parentReady'<\/span> &amp;&amp; !parentReady) {\n        <span style=\"color:#0aa\">window<\/span>.clearInterval(poll);\n        <span style=\"color:#00a\">if<\/span> (sendDataLayerMessages) startDataLayerMessageCollection();\n        parentReady = <span style=\"color:#00a\">true<\/span>;\n      }\n    };\n    \n    <span style=\"color:#00a\">var<\/span> pollCallback = <span style=\"color:#00a\">function<\/span>() {\n      <span style=\"color:#aaa;font-style:italic\">\/\/ If maximum time is reached, stop polling\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>      maxTime -= pollInterval;\n      <span style=\"color:#00a\">if<\/span> (maxTime 0) <span style=\"color:#0aa\">window<\/span>.clearInterval(poll);\n      <span style=\"color:#aaa;font-style:italic\">\/\/ Send message to parent that iframe is ready to retrieve Client ID\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>      <span style=\"color:#0aa\">window<\/span>.top.postMessage(<span style=\"color:#a50\">'childReady'<\/span>, parentOrigin);\n    };\n    \n    <span style=\"color:#00a\">var<\/span> createMessage = <span style=\"color:#00a\">function<\/span>(obj) {\n      <span style=\"color:#00a\">if<\/span> (!<span style=\"color:#0aa\">Array<\/span>.isArray(obj) &amp;&amp; <span style=\"color:#00a\">typeof<\/span> obj === <span style=\"color:#a50\">'object'<\/span>) {\n        <span style=\"color:#00a\">var<\/span> flattenObj = JSON.parse(JSON.stringify(obj));\n\t\t<span style=\"color:#00a\">var<\/span> message = {};\n        <span style=\"color:#aaa;font-style:italic\">\/\/ Add metadata about the page into the message\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        message[dataLayerMessagePrefix] = {\n          pageData: {\n            url: <span style=\"color:#0aa\">document<\/span>.location.href,\n            title: <span style=\"color:#0aa\">document<\/span>.title\n          }\n        };\n        <span style=\"color:#00a\">for<\/span> (<span style=\"color:#00a\">var<\/span> prop <span style=\"color:#00a\">in<\/span> flattenObj) {\n          <span style=\"color:#00a\">if<\/span> (flattenObj.hasOwnProperty(prop) &amp;&amp; prop !== <span style=\"color:#a50\">'gtm.uniqueEventId'<\/span>) {\n            <span style=\"color:#00a\">if<\/span> (prop === <span style=\"color:#a50\">'event'<\/span>) {\n              message.event = dataLayerMessagePrefix + <span style=\"color:#a50\">'.'<\/span> + flattenObj[prop];\n            } <span style=\"color:#00a\">else<\/span> {\n              message[dataLayerMessagePrefix][prop] = flattenObj[prop];\n            }\n          }\n        }\n        <span style=\"color:#00a\">if<\/span> (!message.event) message.event = dataLayerMessagePrefix + <span style=\"color:#a50\">'.Message'<\/span>;\n        <span style=\"color:#00a\">return<\/span> message;\n      }\n      <span style=\"color:#00a\">return<\/span> <span style=\"color:#00a\">false<\/span>;\n    };\n    \n    <span style=\"color:#00a\">var<\/span> startDataLayerMessageCollection = <span style=\"color:#00a\">function<\/span>() {\n      <span style=\"color:#aaa;font-style:italic\">\/\/ Send the current dataLayer content to top frame, flatten the object\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>      <span style=\"color:#0aa\">window<\/span>.dataLayer.forEach(<span style=\"color:#00a\">function<\/span>(obj) {\n        <span style=\"color:#00a\">var<\/span> message = createMessage(obj);\n        <span style=\"color:#00a\">if<\/span> (message) <span style=\"color:#0aa\">window<\/span>.top.postMessage(message, parentOrigin);\n      });\n      <span style=\"color:#aaa;font-style:italic\">\/\/ Create the push listener for future messages\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>      <span style=\"color:#00a\">var<\/span> oldPush = <span style=\"color:#0aa\">window<\/span>.dataLayer.push;\n      <span style=\"color:#0aa\">window<\/span>.dataLayer.push = <span style=\"color:#00a\">function<\/span>() {\n        <span style=\"color:#00a\">var<\/span> states = [].slice.call(arguments, <span style=\"color:#099\">0<\/span>);\n        states.forEach(<span style=\"color:#00a\">function<\/span>(arg) {\n          <span style=\"color:#00a\">var<\/span> message = createMessage(arg);\n          <span style=\"color:#00a\">if<\/span> (message) <span style=\"color:#0aa\">window<\/span>.top.postMessage(message, parentOrigin);\n        });\n        <span style=\"color:#00a\">return<\/span> oldPush.apply(<span style=\"color:#0aa\">window<\/span>.dataLayer, states);\n      };\n    };\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Start polling the parent page with \"childReady\" message\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> poll = <span style=\"color:#0aa\">window<\/span>.setInterval(pollCallback, pollInterval);\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Start listening for messages from the parent page\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#0aa\">window<\/span>.addEventListener(<span style=\"color:#a50\">'message'<\/span>, postCallback);\n  })();\n<span style=\"color:#1e90ff;font-weight:bold\">script<\/span>&gt;<\/code><\/pre>\n<\/div>\n<p>The following chapters will walk through this code. You can skip right to <a href=\"#google-analytics-tags\">configuring the Google Analytics tags<\/a> if you don\u2019t care about the walkthrough.<\/p>\n<h4 id=\"configuration-1\">Configuration<\/h4>\n<p>This is the first code block:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#aaa;font-style:italic\">\/\/ If not in iframe, do nothing\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">try<\/span> {\n  <span style=\"color:#00a\">if<\/span> (<span style=\"color:#0aa\">window<\/span>.top === <span style=\"color:#0aa\">window<\/span>.self) <span style=\"color:#00a\">return<\/span>;\n} <span style=\"color:#00a\">catch<\/span>(e) {}\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Set to false to prevent dataLayer messages from being sent to parent\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">var<\/span> sendDataLayerMessages = <span style=\"color:#00a\">true<\/span>;\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Set the prefix that will be used in the event name, and under which all\n<\/span><span style=\"color:#aaa;font-style:italic\">\/\/ the dataLayer properties will be embedded\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">var<\/span> dataLayerMessagePrefix = <span style=\"color:#a50\">'iframe'<\/span>;\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Set to parent origin (\"https:\/\/www.domain.com\")\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">var<\/span> parentOrigin = <span style=\"color:#a50\">'https:\/\/www.parent-domain.com'<\/span>;\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Maximum time in milliseconds to poll the parent frame for ready signal\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">var<\/span> maxTime = <span style=\"color:#099\">2000<\/span>;\n<\/code><\/pre>\n<\/div>\n<p>First of all, if the page is <strong>not<\/strong> loaded in an <code><iframe><\/iframe><\/code>, the code does not and should not execute at all. There\u2019s no parent page to communicate with, so executing any of the following code would be unnecessary.<\/p>\n<p>You can set <code>sendDataLayerMessages<\/code> to <code>false<\/code> if you <strong>don\u2019t<\/strong> want to forward the <code>dataLayer<\/code> messages from the child to the parent. This is useful if you\u2019re comfortable with tracking everything within the <code><iframe><\/iframe><\/code> itself, and don\u2019t want to bother with setting up the corresponding tags in the parent page.<\/p>\n<p>The <code>dataLayerMessagePrefix<\/code> value is what will be used to namespace the <code>dataLayer<\/code> messages that are forwarded from the child to the parent (if you haven\u2019t disabled forwarding per the previous paragraph).<\/p>\n<p>The <code>parentOrigin<\/code> should be set to the <strong>origin<\/strong> of the parent page, i.e. the page to which the messages are sent. Remember, <strong>origin<\/strong> is everything from the protocol to the first slash of the path component. In other words, the <strong>origin<\/strong> of <code>https:\/\/www.simoahava.com\/some-page<\/code> is <code>https:\/\/www.simoahava.com<\/code>.<\/p>\n<p>Finally, the <code>maxTime<\/code> is how long the <code><iframe><\/iframe><\/code> tries to send the <code>childReady<\/code> message to the parent before it stops.<\/p>\n<h4 id=\"the-poller\">The poller<\/h4>\n<p>The child frame polls the parent page with the <code>childReady<\/code> message to signal it\u2019s ready for the bilateral communication to start.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">var<\/span> pollCallback = <span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#aaa;font-style:italic\">\/\/ If maximum time is reached, stop polling\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  maxTime -= pollInterval;\n  <span style=\"color:#00a\">if<\/span> (maxTime 0) <span style=\"color:#0aa\">window<\/span>.clearInterval(poll);\n  <span style=\"color:#aaa;font-style:italic\">\/\/ Send message to parent that iframe is ready to retrieve Client ID\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>  <span style=\"color:#0aa\">window<\/span>.top.postMessage(<span style=\"color:#a50\">'childReady'<\/span>, parentOrigin);\n};\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Start polling the parent page with \"childReady\" message\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">var<\/span> poll = <span style=\"color:#0aa\">window<\/span>.setInterval(pollCallback, pollInterval);\n<\/code><\/pre>\n<\/div>\n<p>It runs every 200 milliseconds until the maximum poll time (2000 milliseconds by default) is reached.<\/p>\n<h4 id=\"the-listener-1\">The listener<\/h4>\n<p>At the same time as it starts polling, a <code>postMessage<\/code> listener is also initiated. This listener waits for two things:<\/p>\n<ol>\n<li>The parent to signal <code>parentReady<\/code>, so that the <code><iframe><\/iframe><\/code> can start forwarding its <code>dataLayer messages to the parent<\/code>.<\/li>\n<li>The parent to return a Client ID string, so that the <code><iframe><\/iframe><\/code> can use this in Google Analytics tags.<\/li>\n<\/ol>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">var<\/span> postCallback = <span style=\"color:#00a\">function<\/span>(event) {\n  <span style=\"color:#00a\">if<\/span> (event.origin !== parentOrigin) <span style=\"color:#00a\">return<\/span>;\n  <span style=\"color:#00a\">if<\/span> (event.data.event !== <span style=\"color:#a50\">'clientId'<\/span> &amp;&amp; event.data !== <span style=\"color:#a50\">'parentReady'<\/span>) <span style=\"color:#00a\">return<\/span>;\n\n  <span style=\"color:#00a\">if<\/span> (event.data.event === <span style=\"color:#a50\">'clientId'<\/span>) {\n    <span style=\"color:#0aa\">window<\/span>.dataLayer.push({\n      event: <span style=\"color:#a50\">'clientId'<\/span>,\n      clientId: event.data.clientId\n    });\n  }\n\n  <span style=\"color:#00a\">if<\/span> (event.data === <span style=\"color:#a50\">'parentReady'<\/span> &amp;&amp; !parentReady) {\n    <span style=\"color:#0aa\">window<\/span>.clearInterval(poll);\n    <span style=\"color:#00a\">if<\/span> (sendDataLayerMessages) startDataLayerMessageCollection();\n    parentReady = <span style=\"color:#00a\">true<\/span>;\n  }\n};\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ Start listening for messages from the parent page\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#0aa\">window<\/span>.addEventListener(<span style=\"color:#a50\">'message'<\/span>, postCallback);\n<\/code><\/pre>\n<\/div>\n<p>If the parent sends a <code>clientId<\/code> message, then the page pushes this data into <code>dataLayer<\/code>, and Google Analytics tags firing in the <code><iframe><\/iframe><\/code> can utilize this information in their settings.<\/p>\n<p>If the parent sends a <code>parentReady<\/code> message, the <code><iframe><\/iframe><\/code> stops sending the <code>childReady<\/code> message. Then, it fires up the <code>dataLayer<\/code> message forwarder (unless the user has chosen to prevent this).<\/p>\n<h4 id=\"the-message-forwarder\">The message forwarder<\/h4>\n<p>The forwarder comprises two methods: <code>createMessage(obj)<\/code> and <code>startDataLayerMessageCollection()<\/code>.<\/p>\n<p>Without going into too much detail, the basic setup is this:<\/p>\n<ol>\n<li>The <code>createMessage()<\/code> method is a utility that wraps the <code>dataLayer<\/code> message from the <code><iframe><\/iframe><\/code> page with the prefix configured in the beginning of the Custom HTML tag (<code>\"iframe\"<\/code>) by default. This prefix is used with the event name (so <code>gtm.js<\/code> becomes <code>iframe.gtm.js<\/code>) as well as with the object itself (so <code>{key: 'value'}<\/code> becomes <code>{iframe: {key: value}}<\/code>).<\/li>\n<li>The <code>createMessage()<\/code> method also automatically adds a <code>pageData<\/code> object which contains the <code>url<\/code> and <code>title<\/code> of the page in the <code><iframe><\/iframe><\/code>.<\/li>\n<li>The <code>startDataLayerMessageCollection()<\/code> method first sends everything in the <code>dataLayer<\/code> array collected thus far to the parent.<\/li>\n<li>Then the method rewrites the <code>dataLayer.push<\/code> function to send everything pushed into <code>dataLayer<\/code> to the parent page as well.<\/li>\n<\/ol>\n<p>In other words, everything added to the <code>dataLayer<\/code> array within the <code><iframe><\/iframe><\/code> is forwarded to the parent page, so that the parent page can handle tracking of interactions and events within the <code><iframe><\/iframe><\/code> as well.<\/p>\n<p>For example, here\u2019s how the forwarded transpiles and sends a <code>dataLayer<\/code> message to the parent page.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#aaa;font-style:italic\">\/\/ This is what's pushed into dataLayer:\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>{\n  event: <span style=\"color:#a50\">'userLogin'<\/span>,\n  user: {\n    id: <span style=\"color:#a50\">'abcd-1234'<\/span>,\n    status: <span style=\"color:#a50\">'gold'<\/span>\n  }\n}\n\n<span style=\"color:#aaa;font-style:italic\">\/\/ This is what's sent to the parent page\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>{\n  event: <span style=\"color:#a50\">'iframe.userLogin'<\/span>,\n  iframe: {\n    pageData: {\n      url: <span style=\"color:#a50\">'https:\/\/www.iframe-domain.com\/iframe-page\/'<\/span>,\n      title: <span style=\"color:#a50\">'Iframe Page Title'<\/span>\n    },\n    user: {\n      id: <span style=\"color:#a50\">'abcd-1234'<\/span>,\n      status: <span style=\"color:#a50\">'gold'<\/span>\n    }\n  }\n}\n<\/code><\/pre>\n<\/div>\n<h3 id=\"google-analytics-tags\">Google Analytics tags<\/h3>\n<p>If you <strong>do<\/strong> want to collect data to Google Analytics from within the <code><iframe><\/iframe><\/code> page, you need to configure <strong>all<\/strong> your Google Analytics tags with the following settings:<\/p>\n<div style=\"aspect-ratio: 1786 \/ 1138;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/02\/iframe-google-analytics-settings.jpg\" title=\"Iframe google analytics settings\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1138\" width=\"1786\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/02\/iframe-google-analytics-settings.jpg#ZgotmplZ\" alt=\"Iframe google analytics settings\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>As the <code><iframe><\/iframe><\/code> will no longer use cookies to persist the Client ID, you need to set the <code>storage<\/code> field value to <code>none<\/code>.<\/p>\n<p>Then, because the child frame uses the Client ID pushed into <code>dataLayer<\/code> by the message listener, you\u2019ll need to update the <code>clientId<\/code> field to use a Data Layer variable for <code>clientId<\/code>. It should look like this:<\/p>\n<div style=\"aspect-ratio: 2354 \/ 1208;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/02\/dlv-clientid.jpg\" title=\"Client ID in Data Layer\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1208\" width=\"2354\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/02\/dlv-clientid.jpg#ZgotmplZ\" alt=\"Client ID in Data Layer\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>None of your tags should fire if the Client ID is not yet available. You can use the <code>clientId<\/code> in a Custom Event trigger to fire your tags when Client ID has been pushed to <code>dataLayer<\/code>.<\/p>\n<div style=\"aspect-ratio: 2258 \/ 1080;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/02\/custom-event-trigger.jpg\" title=\"Custom Event trigger\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1080\" width=\"2258\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/02\/custom-event-trigger.jpg#ZgotmplZ\" alt=\"Custom Event trigger\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You can also update your existing triggers to not fire until <code>clientId<\/code> has a valid value.<\/p>\n<div style=\"aspect-ratio: 1862 \/ 1126;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/02\/clientid-not-undefined.jpg\" title=\"Client ID not undefined\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1126\" width=\"1862\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/02\/clientid-not-undefined.jpg#ZgotmplZ\" alt=\"Client ID not undefined\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h4 id=\"multi-purpose-container\">Multi-purpose container<\/h4>\n<p>One important caveat to note is that if you set up all your tags per the instructions in the previous chapter, then the container running in the <code><iframe><\/iframe><\/code> will not function properly if the page is visited directly as a top-level page, i.e. not as an embed.<\/p>\n<p>I would generally recommend to avoid mixing too many use cases in a single container. It might be wiser to create a <em>different<\/em>  container for scenarios where the page is visited directly, and have the developers update the site code to load the correct container, depending on whether the page is embedded in an <code><iframe><\/iframe><\/code> or not.<\/p>\n<p>However, if you do want the container to cater to different use cases, then a good idea is to create a separate set of tags for when the page is accessed in the top frame vs. when the page is accessed as an embed.<\/p>\n<p>You can use a simple utility variable to check if the page is accessed as an <code><iframe><\/iframe><\/code> or not. This Custom JavaScript variable returns <code>true<\/code> if the page <strong>is NOT in an <code><iframe><\/iframe><\/code><\/strong> and <code>false<\/code> otherwise.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#00a\">try<\/span> {\n    <span style=\"color:#00a\">return<\/span> <span style=\"color:#0aa\">window<\/span>.top === <span style=\"color:#0aa\">window<\/span>.self;\n  } <span style=\"color:#00a\">catch<\/span>(e) { <span style=\"color:#00a\">return<\/span> <span style=\"color:#00a\">false<\/span>; }\n}\n<\/code><\/pre>\n<\/div>\n<p>You can then check for this variable value in your tags, triggers, and variables, to make sure that tracking is configured correctly depending on how the page is accessed.<\/p>\n<h2 id=\"summary\">Summary<\/h2>\n<p>This has been a difficult article to write, and I feel a bit embarrassed to leave so much work to you, my noble reader.<\/p>\n<p>The thing is &#8211; working with this type of a bilateral messaging setup requires both the parent page and the <code><iframe><\/iframe><\/code> page to be in sync. The Custom HTML tags I\u2019ve designed have been built to naturally reject race conditions, but it\u2019s possible you\u2019ll need to modify one or the other (or both) to make things work on your site.<\/p>\n<p>Over the course of my career, and especially over the time I\u2019ve been blogging about Google Tag Manager, trying to perfect tracking of <code><iframe><\/iframe><\/code> elements has been my personal Mount Everest. Cross-site tracking of embedded content has take over my life to such an extent that I\u2019m losing sleep and seriously considering hunting down Ibrahim or Isabella Frame, or whoever the person is who thought the <code><iframe><\/iframe><\/code> is a great addition to the HTML spec.<\/p>\n<p>Regardless, it\u2019s still <em>such a common use case<\/em> on the SaaS- and micro service -rich web to embed cross-site content. Naturally, there\u2019s an incentive to know what happens in these <code><iframe><\/iframe><\/code> blocks that I consider the time I\u2019ve spent trying to solve this particular riddle to be almost worthwhile.<\/p>\n<p>Anyway, I expect <em>and hope<\/em> that you have comments and\/or questions about this setup. Let\u2019s continue the discussion in the comments of this article, thank you!<\/p>\n<\/p><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Updated 25 May 2021: Added information about using this with GA4. As Google Analytics 4 does not have a mechanism to disable cookie storage, only the second solution (send dataLayer events from iframe to the parent) described in this article will work for GA4. Here I am, back with and cross-domain tracking. I\u2019ve published a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":91328,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[32844,44021,44022,1706],"dealstore":[],"offerexpiration":[],"class_list":["post-91327","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-cookieless","tag-crosssite","tag-iframes","tag-tracking"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Cookieless Tracking For Cross-site Iframes - Som2ny Network<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fivemor.com\/?p=91327\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Cookieless Tracking For Cross-site Iframes - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"Updated 25 May 2021: Added information about using this with GA4. As Google Analytics 4 does not have a mechanism to disable cookie storage, only the second solution (send dataLayer events from iframe to the parent) described in this article will work for GA4. Here I am, back with and cross-domain tracking. I\u2019ve published a [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=91327\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-02-16T08:44:07+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/iframe-postmessage.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1396\" \/>\n\t<meta property=\"og:image:height\" content=\"472\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"22 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=91327#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=91327\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"Cookieless Tracking For Cross-site Iframes\",\"datePublished\":\"2025-02-16T08:44:07+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=91327\"},\"wordCount\":3224,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=91327#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/iframe-postmessage.jpg\",\"keywords\":[\"cookieless\",\"Crosssite\",\"Iframes\",\"Tracking\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=91327#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=91327\",\"url\":\"https:\/\/fivemor.com\/?p=91327\",\"name\":\"Cookieless Tracking For Cross-site Iframes - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=91327#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=91327#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/iframe-postmessage.jpg\",\"datePublished\":\"2025-02-16T08:44:07+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=91327#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=91327\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=91327#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/iframe-postmessage.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/iframe-postmessage.jpg\",\"width\":1396,\"height\":472},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=91327#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Cookieless Tracking For Cross-site Iframes\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fivemor.com\/#website\",\"url\":\"https:\/\/fivemor.com\/\",\"name\":\"Som2ny Network\",\"description\":\"Daily Deals\",\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fivemor.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fivemor.com\/#organization\",\"name\":\"Som2ny Network\",\"url\":\"https:\/\/fivemor.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"width\":300,\"height\":86,\"caption\":\"Som2ny Network\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"caption\":\"admin\"},\"sameAs\":[\"https:\/\/fivemor.com\"],\"url\":\"https:\/\/fivemor.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Cookieless Tracking For Cross-site Iframes - Som2ny Network","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fivemor.com\/?p=91327","og_locale":"en_US","og_type":"article","og_title":"Cookieless Tracking For Cross-site Iframes - Som2ny Network","og_description":"Updated 25 May 2021: Added information about using this with GA4. As Google Analytics 4 does not have a mechanism to disable cookie storage, only the second solution (send dataLayer events from iframe to the parent) described in this article will work for GA4. Here I am, back with and cross-domain tracking. I\u2019ve published a [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=91327","og_site_name":"Som2ny Network","article_published_time":"2025-02-16T08:44:07+00:00","og_image":[{"width":1396,"height":472,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/iframe-postmessage.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"22 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=91327#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=91327"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"Cookieless Tracking For Cross-site Iframes","datePublished":"2025-02-16T08:44:07+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=91327"},"wordCount":3224,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=91327#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/iframe-postmessage.jpg","keywords":["cookieless","Crosssite","Iframes","Tracking"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=91327#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=91327","url":"https:\/\/fivemor.com\/?p=91327","name":"Cookieless Tracking For Cross-site Iframes - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=91327#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=91327#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/iframe-postmessage.jpg","datePublished":"2025-02-16T08:44:07+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=91327#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=91327"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=91327#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/iframe-postmessage.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/iframe-postmessage.jpg","width":1396,"height":472},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=91327#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"Cookieless Tracking For Cross-site Iframes"}]},{"@type":"WebSite","@id":"https:\/\/fivemor.com\/#website","url":"https:\/\/fivemor.com\/","name":"Som2ny Network","description":"Daily Deals","publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fivemor.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fivemor.com\/#organization","name":"Som2ny Network","url":"https:\/\/fivemor.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","width":300,"height":86,"caption":"Som2ny Network"},"image":{"@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","caption":"admin"},"sameAs":["https:\/\/fivemor.com"],"url":"https:\/\/fivemor.com\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/91327","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=91327"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/91327\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/91328"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=91327"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=91327"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=91327"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=91327"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=91327"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}