Workaround for when the hitCallback function does not receive a response (analytics.js)

    Updated with common adblockers / privacy filters (Jan 2014)

    I’ve found in some circumstances when sending a command using Google Universal analytics.js, code sitting inside the hitCallback function does not execute.

    So it might appear that the hitCallback function isn’t working. Well actually, it’s working just fine (sort-of).

    It turns out I had an AdBlocker running which prevented analytics.js from sending the data, and as result, the hitCallback function not firing.

    A number of most common AdBlockers / Browser Privacy filters used are:

    If you’re having issues, I’d suggest you start debugging with Ghostery first, this software is the most comprehensive analytics blocker out the there listed.

    I had a look at the ga object. This is what it looks like when it loads successfully:

    And what it looks like when it doesn’t load:

    Based on this, I developed the quick solution below:

    var url = "http://www.domsammut.com/";
    
    /*
     * Simply checks that the loaded property exists 
     * and that it has a value of true. 
     */
    if (ga.hasOwnProperty('loaded') && ga.loaded === true) {
    
        //Success, ga is loaded
    
        ga('send', 'event', {
            'eventCategory' : 'Outbound',
            'eventAction' : 'Link',
            'eventLabel' : url,
            'eventValue' : 1,
            'hitCallback' : function () {
                document.location = url;
            }
        });
    
    } else {
    
        //Not loaded, continue without tracking the data
        document.location = url;
    
    }

    While in the past, if a user hasn’t been able to load Google Analytics, it hasn’t been a major concern as the send command fails silently. While this is still the case with analytics.js, you can hook code into Google Analytics that is dependant on successfully completing the send command.

    Note: This doesn’t solve the problem above, since the analytics.js script was not able to load at all.

    Ideally, Google would expand this function so that you could pass a timeout value to execute your function regardless.

    ga('send', 'event', {
        'eventCategory' : 'Outbound',
        'eventAction' : 'Link',
        'eventLabel' : url,
        'eventValue' : 1,
        'hitCallbackTimeout' : 500, // ms
        'hitCallback' : function () {
            document.location = url;
        }
    });

    Or have another function that gets triggered after X seconds with no response (time either defined by Google or optionally overridden by user).

    ga('send', 'event', {
        'eventCategory' : 'Outbound',
        'eventAction' : 'Link',
        'eventLabel' : url,
        'eventValue' : 1,
        'hitCallback' : function () {
            document.location = url;
        },
        'hitCallbackFail' : function () {
            alert("Unable to send Google Analytics data");    
        }
    });

    In the mean time, hopefully this solution helps you out a little.

    This post was last modified on March 3, 2016 8:41 am

    Dom Sammut: Dom Sammut is a PHP / Node.js Web Developer from Australia with extensive experience in developing in and customising Laravel, Express, VueJS, WordPress, Symphony CMS, Craft CMS and Squiz Matrix.

    View Comments (9)

    • Hi,

      Many thanks for your post which helped me get across :)

      In my case, I had to check if ga actually existed and was an object or it would blow.
      So I ended up with this check: if (typeof ga === 'object' && ga.hasOwnProperty('loaded') && ga.loaded === true)

    • I was going crazy with the hitCallback redirection not working. In my case, uBlock Origin was the problematic ad blocker.

      Thanks.

      • Thanks for this Lawrence, I've added this to the list of possible interfering trackers :)

    • I have seen occasions where the analytics.js is loaded but the GA send event is filtered. Therefore I would suggest refining the code by adding a backup routine that is triggered by setTimeout:

      var trackOutboundLink = function(link) {
          if (ga.hasOwnProperty('loaded') && ga.loaded === true) {
              setTimeout(function() {
                  document.location.href = link.href;
              }, 1000);
              ga('send', 'event', 'outbound', 'click', link.href, {'hitCallback':
                  function () {
                      document.location.href = link.href;
                  }
              });
          } else {
              document.location.href = link.href;
          }
      }
      

      Invoked using:

      ...
      

      Example page at http://bwipp.terryburton.co.uk/

      As an aside, the nice thing about using "trackOutboundLink(this);return false;" is that you can decorate all external <a> tags with this onclick method with a DOMContentLoaded event handler.

      • Good idea Terry regarding the timeout fallback. Do you have an example / scenario on how to reproduce the issue? I've yet to see this happen before under normal circumstances (Normal circumstances being Google Analytics initialising and a persistent internet connection).

        • I had two users reporting that my "links were broken" within a week of switching a high-volume site over to using a callback solution similar to your own.

          One individual was using their own laptop on a corporate network and the other was browsing from a wifi hotspot. So this would likely indicate interference by an application layer proxy.

          • Ah okay. So was the Google Analytics script able to be initialised? It would seem odd that they could make a request to get the analytics.js script and have it initialised but then not make the get request for the event. The script and the get request are both made to the same domain google-analytics.com. If you have ga('require', 'displayfeatures');, it will make requests to stats.g.doubleclick.net, that could cause the hitCallback not to fire if doubleclick.net is blocked and hence can't successfully make the get request. Good to know though, I think the setTimeout is a prudent measure to add in case it fails for some reason though.

    Related Post