iFrame Google Tag Manager Cross domain tracking alternative

Hi to all who have dealt with the cross domain issue when working with iFrames – it can be a tedious endeavour yet using Google Tag Manager and Universal Analytics the process is somewhat simplified. This is also an updated version of a similar method yet this one uses the Google Analytics Template tag inside GTM.

There already are some great articles which offer a complete set up workflow such as a great Article by Claudia Kosny found on Knewledge.com – http://goo.gl/r8qJED – yet we found it to be a bit too complex when working without a decent developer support and the main issues were:

  • iFrame will not show if Google servers do not respond and
  • iFrame will not show if the GTM setup was misconfigured

If the iFrame was on the site to render some critical information such as Booking engine or any kind of Payment service it was just too big of a risk.
Fortunately Google updated the iFrame section of cross domain tracking with a new method which does not have this kind of problem – Tracking Cross Domain iFrames using postMessage – http://goo.gl/fRV4nB

Downside to this method is:

  • users with IE7 and earlier will be tracked as different clientIDs (cross domain tracking will not be in effect yet tracking will still occur)

To be clear the end goal of this implementation is to preserve the session and have accurate attribution reporting inside Google Analytics – this of course includes the ability to set up funnels and any additional hits within the existing session (without messing up traffic sources and session inflation when using Universal Analytics).

How to set up iFrame cross domain tracking for Universal Analytics via GTM

1. Referral exclusion

The first step is to always prepare the referral exclusion list. Our example will use the domains domainA.com (parent domain) and domainB.com (child domain – page on domain inside iFrame).

2. GTM container

Place the GTM container snippet on both domains! More info can be found in Google Help center http://goo.gl/HA9O9o.

3. GTM settings (GTM Tags, triggers, variables and more)

Triggers

Triggers help you define when a tag should be activated using various conditions.

1. all pages parent domainA.com (parent domain)
A tag using this trigger will activate only on pages with hostname domainA.com (our parent page). You can opt to change the trigger type to either DOM or Page View just make sure you test the setup.
{{Page Hostname}} > match case (can be any which defines the situation) > domainA.com

all pages parent trigger

2. all pages child domainB.com (child domain – inside iFrame)
A tag using this trigger will activate only on pages with hostname domainB.com (our iFrame page).
{{Page Hostname}} > match case (can be any which defines the situation) > domainB.com

all pages child trigger

3. event on child domainB.com trackPage (fires the page on child domain after it has dealt with the cid retrieval)
A tag using this trigger will activate only on pages with hostname domainB.com (optional) and only when the custom event trackPage has been pushed to dataLayer.
Event name: trackPage
{{Page Hostname}} > match case (can be any which defines the situation) > domainB.com (optional)

event child domain trigger

Variables

The following examples are basically utility Variables with which you can easily reuse the entire setup in just a few edits – in short you do not need to mess with the code just edit everything with variables.

1. dl cid (Data Layer)
A Data Layer type variable which returns value of cid once it is pushed to dataLayer.

2. dl virtualPagePath and dl virtualPageTitle (Data Layer)
Just an example of what you can additionally define for tags e.g. booking steps or similar.

3. gtm allowedOrigins (Constant)
Used to set the whitelisted sites (i.e. your booking engine, payment system, CRM) which can communicate to our parent Domain.
Value: http://www.domainB.com,https://www.domainB.com (a list of comma separated domains where our page inside iFrame is placed)

4. gtm topOrigin (Constant)
Used to define the domain which holds our parent Page – parent domain.
Value: http://www.domainA.com (our main domain)

5. gtm uaTrackingId (Constant)
We will store the UA tracking ID (property ID) in a Macro so we can reuse it all of our GA tags with less chance of human error:) (you can of course change it to lookup, dl or custom JS)

Tags

We will need 3 separate tags – 2 Custom HTML and one plain Universal Pageview tag for the complete setup to work. Custom HTML Tags are used to retrieve the cid parameter (code is provided in the full container download). Do not forget to add the clientId under fields to set!

Custom HTML #1 (on child domain only (inside iFrame)):

// Add the expected origin domain inside gtm topOrigin variable
// Must use the following format http://example.com
var topOrigin = {{gtm topOrigin}}; 
function xDomainHandler(event) {
  event = event || window.event;
  var origin = event.origin;
  if (topOrigin != '*' && topOrigin != event.origin) {
    return;
  }
  try {
    var data = JSON.parse(event.data);
  } catch (e) {
    // SyntaxError or JSON is undefined.
    return;
  }
  if (data.cid) {
    sendHit(data.cid);
  }
}

if (window.addEventListener) {
  window.addEventListener('message', xDomainHandler, false);
} else if (window.attachEvent) {
  window.attachEvent('onmessage', xDomainHandler);
}

var alreadySent = false;
function sendHit(cid) {
  if (alreadySent) return;
  alreadySent = true;
  // If cid exists, it will overwrite any existing values
  var params = {};
  if (cid) params['clientId'] = cid;
  
  dataLayer.push({'event':'trackPage','cid': cid});
}
if (!window.postMessage) {
  // If no postMessage Support.
  sendHit();
} else {
  // Tell top that we are ready.
  top.postMessage('send_client_id', topOrigin);
  // Set a timeout in case top doesn't respond.
  setTimeout(sendHit, 100);
}

 

Custom HTML #2 (on parent domain only ):

// Edit the gtm allowedOrigins variable and add your domains to the whitelist
var allowedOrigins = {{gtm allowedOrigins}}.split(',');
function xDomainHandler(event) {
  event = event || window.event;
  var origin = event.origin;
  
// Check for the whitelist.
  var found = false;
  for (var i = 0; i < allowedOrigins.length; i++) {
    if (allowedOrigins[i] == origin) {

      found = true;
      break;
    }
  }
  if (!found) return;
  if (event.data != 'send_client_id') return;

// Get the clientId and send the message.
  var tracker = ga.getAll()[0];
    tracker.get('clientId');
    var data = {cid: tracker.get('clientId')};
    event.source.postMessage(JSON.stringify(data), origin);
}
if (window.addEventListener) {
  window.addEventListener('message', xDomainHandler, false);
} else if (window.attachEvent) {
  window.attachEvent('onmessage', xDomainHandler);
}

 

The final tag setup will look like this:

cross domain tags

 

 

 

ua pv main tag

 

 

You can download the full container version here – please be careful when you import the  container you do not: overwrite your existing container or even import into a production container rather test it first in a new account / container and migrate settings after a successful testing process.

4. Publish

After you finish all of the setup please do not forget to debug, preview and create a version and publish in the end.
Happy (cross domain) tracking!

18 thoughts on “iFrame Google Tag Manager Cross domain tracking alternative”

  1. Hi Zorin, thanks for the step-by-step guide. I previously used Claudia’s method to track iframe via GTM. It worked fine but suddenly two months ago, somehow the cross domain tracking stopped working and none of my conversion is attributed to the correct sources. When I checked, the iframe code was not changed. I will try to use your method, is there anything that I need to be aware of? Also, do you know if something happened with GTM and iframe tracking a few months ago? thanks so much!

    1. Hi Olivia,

      The only major update to GTM happened a few months ago which was v1 to v2 migration but how that may have affected the set up I could only guess.

      When implementing this method just try to follow the steps or just import the container and if you run into any issues please let me know.

      Kind regards,
      Zorin

      1. Hi Zorin, I downloaded your container version, updated all the hostnames accordingly but when I preview it on my site, somehow the child HTML did not fire on the pages where the iframe is embedded. Instead, the main PV tag and GTM parent domain tag is fired on the pages where the iframe is embedded. Could you help me out here? thanks!

  2. Hi Zorin – Thanks very much for this. It worked great for me. I just modified your method a little as the iframe was using a different GTM container.

  3. I have tried implementing this and got some error in GTM. Could you please help me out

    #ERROR :1 Unknown variable ‘gtm processEnd seconds’ found in a tag. Edit the tag and remove the reference to the unknown variable.

    #ERROR : 2 Unknown variable ‘gtm processEnd’ found in a tag. Edit the tag and remove the reference to the unknown variable.

    #ERROR : 3 Unknown variable ‘gtm sessionStart’ found in another variable. Edit the variable and remove the reference to the unknown variable.

    #ERROR : 4 Unknown variable ‘gtm cid’ found in a tag. Edit the tag and remove the reference to the unknown variable.

  4. Hi All,

    I have set this up for one of my clients but doesn’t seem to be working.

    Any help would be great!

    Kind Regards
    Terry

  5. Hi Zorin

    Great article. I’m hoping you can help, we are in the process of setting up cross domain tracking for one of our clients – however the added complication comes in that they have 4 sites, all with unique GTM containers, which all use the same iFrame. The iframe containers ALL container Tags. Is there away to track the traffic which has only come from one-site within their journey on the iframe. For example:

    Container A, could assign to Analytics Property A only traffic from : siteA.com > iFrame

    Container B, could assign to Analytics Property Bonly traffic from : siteB.com > iFrame traffic

    etc….

    I have tried using the referrer variable which works for the first page within the iFrame, but does not work beyond this first iFrame page. So beyond this pages are left untracked. If i just go with the setup here, then as every container is loaded on the iframe it tracks all visitors to the iframe, independent of who their Parent Site is.

    So what I think I am looking for is something like a landing page variable for their first pageview’s domain. But i cannot find anything like this online & Google’s own support was not forthcoming on a solution or an idea at all.

    Is this something you have come across before?

    Any help or guidance would be great and very much appreciated

    Thanks

    Ben

    1. Hi Ben, this can be done by setting a cookie or sessionStorage once a referrer meets some of the conditions e.g. page hostname != referrer hostname. Once you set this up you can pull the tracking id from the cookie / sessionStorage based on initial referrer hostname. So if site A then assign cookie value UA-12345678-1 and so on …

      Hope it helps!

      1. Hi Zorin,

        Thanks for coming back on this, and this certainly looks like a step in the right direction.

        Looking into the sessionStorage information on a couple of blogs, I can see how it would be really useful. Would you suggestion something along this lines of setting the ‘refferer hostname’ on the initial iframe Landing Page, and then recalling that up on every sub-subsequent page? Also sorry if I’m being a bit slow on this but would i then be able to use the ‘sessionStorage’ referrer hostname as a trigger to (for example):

        only fire the UA code for SiteA, on the iframe, when ‘sessionStorage: referrer host’ = SiteA? Because this is the part which I have had problems with so far.

        As I said above the main obstacle we have at the moment is trying to restrict the UA tags to only-fire in the iframe on traffic from their respective CountrySites. Any help on this is very appreciated, feel like i’ve tried everything!!

        Thanks again
        Ben

        1. Hi Ben, your issue is basically a 2 part problem – 1. assigning the right UA tracking ID on the first touchpoint (first load on the iframed page) – you do this by assigning the UA tracking ID based on referrer hostname – so if siteA then use UA-12345678-1 – this can be done by a simple lookup table with an addition of a customJS variable which does > if a 1st party cookie (name it uaOriginId) does not exist write one using the value from the lookup table variable previously mentioned and the 2. issue is persisting the value which you actually already have solved by using the cookie – so if uaOriginId cookie exist use the value from that instead.

          In simple terms this is a simple custom js variable which needs to be used to supply UA tracking Id on the iframed page:
          function () {
          if (!{{cookie – uaOriginId}}) {
          // write the cookie using the value from the lookup table variable
          return {{util – lookup table uaTrackingId}}; // return the GA trackingid value
          } else {
          return {{cookie – uaOriginId}}; // iff the cookie already exists no need to write a new one and just assign the tracking id
          }
          }

          Hope it helps!

          1. Hi Zorin

            Thank you so much for your help on this. I have tried implementing your suggestion above, but i am still unable to get the UA number to stick . For example in the Ireland Site: We have used the {{cookie – IEuaOriginId}}, in the below code, but whilst I can get the custom JavaScript to return the value on the first instance of the iframe, it does not transfer this value across to the {{cookie – IEuaOriginId}}. I’ve looked into the possibility that we have missed another stage here, but i’m not sure we have.

            So to clarify this below will return the correct value for the JavaScript variable but leaves {{cookie – IEuaOriginId}} undefined still.

            function () {
            if (!{{cookie – IEuaOriginId}}) {
            // write the cookie using the value from the lookup table variable
            return {{util – lookup table IE uaTrackingId}};
            // return the GA trackingid value
            }
            else {
            return {{cookie – IEuaOriginId}}; // iff the cookie already exists no need to write a new one and just assign the tracking id
            }
            }

            Again any assistance you can offer on this would be greatly appreciate.

            Thanks again

            Ben

          2. Hi Ben,

            the situation is quite simple to resolve. You just need to add one more line the code where you actually write the cookie:

            function () {

            if (!{{cookie – IEuaOriginId}}) {
            // write the cookie using the value from the lookup table variable
            document.cookie = “IEuaOriginId=” + {{util – lookup table IE uaTrackingId}} + “; path=/”; // add expiration or whatever you find needed in this case
            //return the initial value from lkp table
            return {{util – lookup table IE uaTrackingId}};
            }
            else {
            return {{cookie – IEuaOriginId}}; // if the cookie already exists no need to write a new one and just assign the tracking id
            }
            }

            Hope it helps!

          3. Hi Zorin

            Thank you so much for your help on this. This last solution has done the business and the 1st Party Cookie is now being transferred across. I have also replicated this out across all the other sites now so hopefully this will work across all sites (I can’t see a reason why not – i tried very hard to break it yesterday to no avail!).

            Thanks again for your help on this, and for explaining this all so well. Will be a big help in the future!

            Ben

          4. Hi Zorin,

            I was wondering, following a similar set up to this, would it be possible to send cid into storage to retrieve once the user has navigated across to an alternative domain?

            I am currently working on a project where it is not possible to track from domain B to domain C. On domain B there is a form that the user must submit in order to transfer across to domain C. It seems that a custom submit function on the form is in place meaning that the _ga is not automatically added to the redirect url, despite use of a form decorate tag. For this reason, cross domain tracking from domain A to B is in place but once the user navigates across to domain C, they are assigned a new cid.

            Would it be possible to amend the set up discussed above to allow the cid to be retrieved on domain C?

            Thank you in advance and apologies for hijacking this thread!

            Kind regards,
            Hannah

Leave a Reply

Your email address will not be published. Required fields are marked *