A quick primer on the nature of the problem
One of the most common problems I run into with Google Analytics (GA) occurs when a website uses a 3rd-party shopping cart, booking system or other transactional functionality. These systems often run on another domain, which means a user goes from xyz.com to xyz.shoppingcart.com to complete a transaction, for example. By default, GA counts this user as two sessions, assuming you have your GA tag placed on xyz.shoppingcart.com. For several reasons, this is not typically what you want. The two most important reasons are:
- User sessions are double-counted, giving you an inflated view of traffic to your website.
- The source of the user on xyz.shoppingcart.com is shown as xyz.com, or direct if you have added xyz.com to your referral exclusion list. This gives the false appearance that traffic from organic search, social media, advertising, etc. doesn’t produce conversions.
With the release of Universal Analytics in 2012, Google introduced the Linker plugin, to solve this problem. It was possible to fix before that, but much more tedious. The Linker plugin automagically transfers the GA user ID from one domain to the other, allowing GA to track visits to both domains as a single session. This works beautifully, most of the time.
Cross-domain tracking in an iFrame
One case where the Linker plugin does not work is when a 3rd-party system is delivered in an iFrame on the website domain. The Linker does not have time to do its automagical goodness on the iFrame link, because the request for the iFrame happens before the Linker has loaded.
Google offers a method to overcome this, but in my experience, it does not work. The method they suggest entails adding a function to xyz.com that sends a JavaScript message event with the user’s clientId to xyz.shoppingcart.com, and also adding a JavaScript event listener to xyz.shoppingcart.com to grab the clientId, then create the GA tracker on xyz.shoppingcart.com. The problem I ran into stems from the fact that JavaScript events are asynchronous. The script that sends the message event doesn’t know if anybody is listening. There is no built-in mechanism to ensure that the message is delivered. So, for the script to work, the event listener has to be running before the event arrives. In the case of the Google iFrame code, this means that the browser has to execute the event listener in the iFrame before it executes the function on xyz.com that sends the event. Seems to me that this would rarely happen. I can say for sure that it does not in cases that I’ve tried.
One simple trick
Luckily, a few small modifications to Google’s sample code fix this problem. If you’ve been following along, it has probably already occurred to you: you need to add a delay to the function that sends the message event, to give the iFrame time to load. Here is a summary of the changes I made, with detailed code samples below:
- I wrapped the function that sends the message event in a setTimeout function.
- I increased the delay in the fallback createTracker function call in the iFrame code. I did this to increase the time window between when the message is sent and when the event listener gets overridden by the fallback.
Those are the important changes. Below are my code snippets with comments.
To be placed on the page that contains the iFrame:
To be placed on the page that loads in the iFrame – this is a modified version of Google’s code that includes a fallback createTracker call:
Once you are done, you can use the GA debugger Chrome add-in to see if the client ID is passing from the parent window to the child frame. When ‘create’ is called on the GA object, you should see the clientId included as a parameter. When you do, go ahead and treat yourself to a victory dance. I did.
If my solution doesn’t work in your situation, I recommend that you read through Luna Metrics’ comprehensive posts on cross-domain tracking and iFrames.