The Problem
As you may know, GA has a 500 hits per session limit, beyond which additional hits are not reported in GA and GA does not automatically start a new session.
This limit applies to page view, event, social, timing hit types, including hits sent with Measurement Protocol requests. Transaction hit types, which are used for traditional ecommerce tracking (i.e. not Enhanced Ecommerce) are not part of this limit. But Enhanced Ecommerce transaction data, which are sent alongside page view or event hit types are.
In most cases, the 500 hits per session limit is not reached. However with the popularity of enhanced ecommerce tracking implementation, video tracking, page scrolling tracking, and other event tracking all made pretty easy to setup via a Tag Management Systems such as Google Tag Manager, we’ve seen more and more sites reaching this hard limit.
In addition, it’s fair to believe that users spending a lot of time on your site, interacting with many elements, are reallyengaged and will eventually convert. So reporting all interactions can be critical to your bottom line KPIs.
The graph below shows the long tail of sessions and transactions (in %) per number of hits for a major NZ retail site:
At the end of the tail, you can see a small spike of sessions, which are all sessions that reached 500 hits or more (also we can see that 0.15% of revenue is generated at the 500th hit). It could be insignificant but what else are we missing? Enhanced ecommerce transactions? Add to cart? Contact requests? Getting a complete picture of the users interactions during their visit is also paramount for any (offline/online) personalisation projects, remarketing, A/B testing experiments, etc.
To estimate how many sessions reach this 500 hits limit, you simply need to create a custom dimension to return a unique session ID (see Simo’s post) and then create a custom report showing Hits (standard metric) per Session ID (custom dimension).
Then if you apply a filter to look at hits = 500, you can see how many % of sessions are reaching this limit, e.g. 2.99% in the example below:
However there are some situations where you don’t have to worry about this:
- If you are using GA360, you can ask for this limit to be raised to 2,000 hits per session. But even then, this might still not be enough…
- The 500 hits limit doesn’t apply for Apps tracking with Firebase, including the recently launched App + Web property. This new property type is still in Beta and missing some important features such as filters, but it will eventually replace the current data model using gtag.js and analytics.js libraries.
The Solution
There is no solution to increase this limit as such.
However what we can do is to force a new session to start at every 501 hits. The solution detailed below assumes you are using GTM to send all GA hits.
It uses the hitCallback function called from the GA settings variable to create and update a 1st party cookie (e.g. ‘gaHitCounter”) storing the number of hits that have been sent to GA. When the 501th hit is reached a new session is forced to start with the sessionControl field.
To be able to stitch session 1 to subsequent session(s), we need to pass the session ID value sent with the 500th hit session to each subsequent hits, using another 1st party cookie (e.g. gaSessionId).
Both gaHitCounter and gaSessionId cookies are deleted/reset when traffic source is not internal to mimic GA’s traffic last non-direct source attribution model.
Hopefully this “state-of-the-art” diagram below is more explicit…
The Recipe
Again, this is assuming all GA hits are sent using GTM.
So to do this, we will need the following ingredients:
Variables:
- GA Settings variable
- hitCallback variable to create gaHitCounter cookie (Custom JS)
- gaHitCounter variable (1st-Party Cookie)
- Session ID variable (Custom JS)
- gaSessionId variable (1s-Party Cookie)
- Session ID – Final variable (RegEx Table)
- gaSessionControl variable (RegEx Table)
- setCookie helper function (Custom JS)
- gaHitCounter as GA CD (Custom JS) – optional but nice to know
Tags:
- Utility tag to create gaSessionId cookie (Custom HTML)
- Utility tag to delete the gaSessionId cookie (Custom HTML)
Triggers:
- gaHitCounter = 499 for the gaSessionId cookie
- Referrer is External and gaSessionId Cookie exists
The Recipe – Details
Variables:
- GA Settings variable
- hitCallback variable to create gaHitCounter cookie (Custom JS)
function() {
return function() {
var selfref = {{Referrer}}.includes("example.com");
// if gaHitCounter Cookie doesn't exist, hitCounter equals 1
if (typeof {{Cookie - gaHitCounter}} == 'undefined') {
{{Set Cookie}}('gaHitCounter', '1', 1800000, '/', 'example.com');
}
// if gaHitCounter Cookie does exist BUT Referrer is NOT example.com, hitCounter equals 1
else if (typeof {{Cookie - gaHitCounter}} !== 'undefined' && selfref == false) {
{{Set Cookie}}('gaHitCounter', '1', 1800000, '/', 'example.com');
}
// if gaHitCounter Cookie exists AND Referrer is example.com, hitCounter is increased by 1
else {
var gaHitCounterValue = parseInt({{Cookie - gaHitCounter}});
var newgaHitCounterValue = gaHitCounterValue;
newgaHitCounterValue++;
{{Set Cookie}}('gaHitCounter', newgaHitCounterValue, 1800000, '/', 'example.com');
}
}
}
- gaHitCounter variable (1st-Party Cookie)
- Session ID variable (Custom JS) – courtesy of Simo Ahava
function() {
return new Date().getTime() + '.' + Math.random().toString(36).substring(5);
}
- gaSessionId variable (1s-Party Cookie)
- Session ID – Final variable (RegEx Table)
- gaSessionControl variable (RegEx Table)
- setCookie helper function (Custom JS) – courtesy of Simo Ahava (see the code for {{JS – setCookie}}
- gaHitCounter as GA CD (Custom JS)
function(){
//replace cd5 with the index number your are using for this custom dimension
if (typeof({{Cookie - gaHitCounter}}) == 'undefined') {
var cd5 = '1';
} else
var cd5 = Number({{Cookie - gaHitCounter}})+1;
return cd5;
}
Tags & Triggers:
- Utility tag to create gaSessionId cookie (Custom HTML)
var sessionIdLast = {{Session ID}}; {{Set Cookie}}('gaSessionId', sessionIdLast, 1800000, '/', 'example.com');
with the following trigger:
- Utility tag to delete the gaSessionId cookie (Custom HTML)
document.cookie = "gaSessionId=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=example.com;";
Note: you may want to set a high “tag firing priority” number (e.g. 9), under Advanced Settings so it is fired before the GA Page View tag.
with the following trigger:
Results
Normally that’s what you should end up with:
In the example above, we have 1 direct session, which reached 500 hits, then a new session (still from Direct) started and contained 3 hits (row 2). You may notice that Hits metric doesn’t align with the Hit Count per Session ID value, That’s because I’ve been lazy and “manipulated” the gaHitCounter value as I couldn’t be bothered spending time clicking around to reach 500 hits! ;-)) So “normally” we should have had 500 Hits in row 1.
And then I left the site and came back (within the 30 minutes session duration expiry) from a link on Facebook. This started a new session as expected with both Session ID and Hit Count per Session ID reset.
Once you have this, you’d need to stitch sessions together with the Session ID to find out how many “true” sessions you’ve had. This can easily be done with Google Data Studio:
Congratulations! You are now able to track hits beyond the 500th one and get a more accurate count of sessions.
Considerations / Limitations
- This “workaround” will artificially increase your sessions metric. Since you “force” a new session to start every 500 hits, you will see more sessions reported, which is better than seeing nothing at all, right?
- You need to re-calculate true sessions by counting unique Session IDs, which does involve an extra step in the reporting process, but this is relatively trivial with Google Data Studio (see above).
- This solution doesn’t work with cross-domain tracking. Since cookies are set at the domain level, the solution won’t work for a cross-domain tracking property, unless you create some logic server-side to store and share cookie values across your different domains.
- Measurement Protocol hits are not taken into account. Since this solution is based on client-side set cookies, hits from your server cannot be considered here (unless again you create the cookies server-side and add some logic to pass the incremented hit counter value to the MP hit, but that might be overkill…
Last but not least: please test and test this again before publishing this live. Each implementation is unique and you might need to customise or even improve this workaround to your needs.