Track Adjusted Bounce Rate In Universal Analytics

So here we are again. Universal Analytics and Google Tag Manager, the dynamic duo, ready to strike again.

First, remember to check my previous two tips for UA and GTM use in custom scenarios:

* Weather as a custom dimension
* Tracking page load time

In this post, I visit the idea of adjusted bounce rate, which I came across a year ago in the Google Analytics blog.

Adjusted bounce rate basically refers to tweaking the traditional bounce rate collection method (single engagement hits / total visits) so that visits which only included a single page view would not count towards a bounce, as long as they met some qualitative requirements.

By the way, check this great post on bounce rate by Yehoshua Coren if you need a refresher on what the term means.

For these custom events, I use the generic event container I created in my previous post.

The easy method: visit duration

This is the easiest to implement. It’s also the one in the Google Analytics blog post I referred to above.

What it does is fire a setTimeout() method as soon as the page has loaded. If the timer runs out (the time is 30 seconds in this script), an interaction event is fired, meaning the visit is not counted as a bounce.

The end result is this:
Calculate visit duration

And here’s how to do it.

1. Create a new custom tag called “Dwell time”
2. Set Tag Type as Custom HTML tag
3. Add the following code in the HTML field:

4. Add {{event}} equals gtm.dom as the firing rule
5. Save tag
6. Save container and publish

See, I told you it was easy. What happens here is that after the DOM has loaded (the firing rule), a timer starts. If the user stays on the page when the timer goes off, the event is fired. Remember, you need the generic event container for this to work. So remember to check my previous post for instructions how to build it (it’s easy, I promise!).

The intermediate method: measure scrolling

This method was inspired by a Google+ post I came across by Avinash Kaushik, where he detailed a script written by Nick Mihailovski. This script is used here extensively, with just the event call in a different format (to support UA and GTM).

What happens here is that the event listener waits for a scroll event (so you actually scroll the page down), and fires the no-bounce event after that. Interesting! The statement is that if you scroll, you read, and if you read, you’re engaged (with the content).

1. Create a new tag “Scroll the page”
2. Set Tag Type as Custom HTML tag
3. Add the following code in the HTML field

4. Set {{event}} equals gtm.dom as the firing rule
5. Save tag
6. Save container and publish

And you’re done. So what the script does is measure if a scroll event occurs during your page view. If it does, the event is fired. Note that it won’t fire the event with every subsequent scroll, so you don’t have to worry about clogging your 500 events per session quotas.

This isn’t fool-proof, of course. It just checks whether the user scrolls. What this DOES prevent is the miscalculation of page visit duration if the user just opens the page in a separate tab and leaves it be. Now you actually need interaction, albeit a very minimal one, to produce an engagement and neutralize the bounce.

The advanced method: page load AND scroll

So what about measuring whether there was a scroll event and the visit duration on the page was over 30 seconds? Wouldn’t that be an even better way to calculate engagement? I think so. So here’s what you do. First, make sure the two tags you just created are not active any more (otherwise you’ll be sending multiple bounce-wrecking events).

1. Create new tag “Dwell and scroll”
2. Set Tag Type as Custom HTML Tag
3. Add the following code in the HTML field:

4. Set {{event}} equals gtm.dom as the firing rule
5. Save tag
6. Save container version and publish

Here the timer starts first. As soon as it hits 30 seconds, it calls the sendBounce() method. This method checks if the user has also scrolled, and if they have, the event is fired. Note that I also make sure that the event is sent only once with the boolean variable bounceSent.

When the user scrolls, the same method is called and the same check is made.

So there are four different scenarios resulting from this script:

1) The user doesn’t scroll, but stays on the page for 0-to-infinite seconds, and the event is not fired (visit is a bounce)
2) The user scrolls, but the timer hasn’t gone off, and the event is not fired (visit is a bounce)
3) The timer goes off, and the user has already scrolled, and the event is fired (visit is not a bounce)
4) The user scrolls, and the timer goes off, and the event is fired (visit is not a bounce)

A much healthier way of calculating adjusted bounce rate, in my opinion.


The way you measure bounce rate should always be in relation to the goals you set for a page or for your site. If engagement is important, remember to add clear calls-to-action, so that you don’t have to resort to artificial adjustments like those depicted in this post.

However, for a simple blog like this, measuring engagement by a combination of visit time and scrolling interaction is probably a pretty good way of getting a more realistic metric for tracking visit quality.

An even more advanced (and qualitative) method would be to see just where the user scrolls to. Is it to the end of the post or just the first paragraph? In other words, does the user read or just skim. That’s a crucial question, and I might just return to the issue in a later post.


  1. Jim says

    Hi Simo,

    The above script look good; I am using similar script for the old tracking code right now and want to switch to Universal.

    I have tried the Easy Method but no luck yet. The Universal status is still Tracking Not Installed, so I should try it again after a couple of days.

    Is it possible to put a copy/paste version of the Advanced Method. It’s so long…

    Thanks for the clear explanation. Very useful!


      • Jim says

        Great! I’m going to try it again since on the Analytics side everything is working.

  2. says

    I’m using the Google Analyticator WordPress which allows inserting code before and/or after the Universal tracking code initialisation. Is this where I could insert your codes if using a plugin. I’d love to have my GA stuff confined in a plugin for better management.
    Thanks for a great article too. I was using something similar for the old GA code.

    • says

      Hi Martin,

      Google Tag Manager is a self-sufficient system, and it’s not optimal to have it running alongside other Google Analytics tracking implementations. So the best way to do it is to:

      * Just have the GTM container code right after the opening tag
      * Not have any other Google Analytics tracking codes on the page template

      You can use GTM to set up Analytics without the need for any WordPress plugins. Sure, you can use a WP-plugin to insert the GTM container code, but other than that, GTM will handle (almost) all your tracking needs.

  3. says

    Hmm, I’ve got a default Tag Manager Universal Analytics tag and added your Dwell and Scroll.
    I can’t see that it is working at all. I would have thought I’d see an Event fire when I look at GA Real Time > Events but I’ve never seen one.
    Am I doing this right or have I missed something? I do love what you’re doing with Tag Manager, great info.

    • says

      Hi Martin,

      Have you created the generic event container?

      You need it for these scripts to work, since they push data in to the data layer, which is then processed and sent via the event tag.

      If you have and you still have problems, here are a few things to check:

      * Are you sure you’re running Universal Analytics on your site?
      * Do other GTM tags work fine?

      If you want more help, send mail to me at and I can take a closer look at your implementation (I’ll need your site URL for the check).

      Best regards,


      • Bryan Mc. says

        i dont understand the instructions, im sorry, do i need to do all the steps listed in your post “Page Load Time In Universal Analytics” or do i skip the Create some macros in GTM and just go with the Create a generic event container instructions listed under that post? if so, what to i need to fill when i create the event container? the same as the create a generic event container example? it will be too much to ask to make a step by step tutorial from start to bottom from creating the event, what to fill, then how to create the dwell tag and how to put them together to make it work? thanks

    • says

      Hamed, all my tutorials are for Universal Analytics, so no ga.js involved here.

      The reason I refer to my old tutorials is because I use macros for event attributes (e.g. category, action, label), and they need to be created before this tutorial works. So read the post on “Page Load Time”, and create the generic event container before you create the custom HTML tags in this post.

      If you don’t have Universal Analytics, you’ll need to upgrade for these tutorials to work. I won’t be writing tutorials for ga.js, since it will be deprecated in the coming months.

    • says

      Hi Kenneth,

      Indeed it does. There’s a lot of things you can do with scroll length. I’ve seen implementations, where there’s scroll length of 25 % of page, 50 % of page, 75 % of page and 100 % of page, and each fire a unique event. This way you could observe just how much your readers are digesting your content.

      The main intent of this script is to weed out immediate bounces and those who just open it in a new tab or leave it be. Like I said, there’s a LOT more that you can do with this idea.

      And you’re right. I’ve been on the lookout for a new template anyway, and that’s a feature that has to be in the new one.

  4. H.Yang says

    Hi Simo,

    Thanks for sharing this great article,

    I just had one problem after implement this code in my site, I getting abnormal high Total events data (48) when I compare it with Total Visits data in All Traffic Acquisition (17). am I doing it wrong? I’m still new in this analytics thing.

    • says


      No, you’re not doing it wrong :) That’s just how the script works at the moment. It sends a “NoBounce” event with every single pageview, which is a bit overkill, but it does its job. Normally it would be enough to just have it send the NoBounce just once per visit (e.g. by using a custom cookie), but understandably this is a bit hard to do. Another way to do it is to check the HTTP Referrer (using the {{referrer}} macro), and if it isn’t your own domain, it means the pageview is a landing page. This doesn’t mean that it’s the first of the visit, since visits can spawn over many exits and entrances, but it would make it a bit more accurate.

      Personally, I don’t see anything wrong with getting these “extra” events, since I can just disregard them in my analysis.

  5. says

    Hi Simo,

    i have a question where you maybe could be of help. When going for the simple solution you use the vent racking for universal analytics. However, if you do not want to reduce your bouncerate as you are potentially then comprmising all your old data i would love to use the noninteraction value. Do oyu think it is possible that this is not working together with the setTimeout. This is the code i was using. Please be aware i am not a developer. Maybe i used the wrong order or is missed an hyphen. However i checked plenty of times.
    setTimeout(“ga(‘send’, ‘event’, ‘nobounce’, ’35 seconds’, {‘nonInteraction’: 1})”,35000);

    Anything wrong?
    Thanks very much and best regards from germany

    • says


      Hm, your code looks good. You might try changing ‘nonInteraction’: 1 to ‘nonInteraction’: true, since the documentation states that the value should be boolean (true/false), even if all the examples use numeric 1 to denote a non-interaction event.

      If that doesn’t work, you might want to use the recommended syntax with something like: setTimeout(“ga(‘set’, ‘nonInteraction’, true);ga(‘send’, ‘event’, ‘nobounce’, ’35 seconds’)”,35000);

  6. says

    Hi Simo, this is absolutely awesome and exactly what I was looking for, thank you!
    We are, however, not using Google Tag Manager at the moment. Would it be possible to get a script to add just below the UA-Tracking Code? (Without use of the Tag Manager)… How would you implement this?

  7. Nipun says


    1st of all thanks a lot for bringing us this awesome post. It indeed is working for us. We have setTimeout(“ga(‘set’, ‘nonInteraction’, true);ga(‘send’, ‘event’, ‘nobounce’, ’50 seconds’)”,50000); and our bounce rate has reduced to 10+- from 60+.

    But this doesn’t stop here and my major concern is something related to sudden shift in Page Views. (Though its an upward shift :))

    Earlier from 25000 Unique Page views I used to get 36000 Page Views but since we have added this code the ratio has improved way ahead. Now we get 55000 Page views from same set of 25000 Unique page views.



    P.S. Also we have done some remarkable changes in our site regarding speed and page load, We just need to know which factor has lead to higher page views.

    • Nipun says

      Sorry it is

      setTimeout(“dataLayer.push({ ‘event’: ‘GAEvent’, ‘eventCategory’: ‘NoBounce’, ‘eventAction’: ‘Over 50 seconds’ })”, 50000);

      • says


        There is nothing in my guide that should start sending double-hits with your pageviews. Are you sure you have just one page view tag, and it doesn’t have multiple rules attached (multiple rules = potentially multiple hits per page load)?

        If you want to give me the URL to your site, I can do some debugging (you can send this privately if you wish).

  8. GP says

    Hi – we have just implemented GTM’s new version. Would we follow the same steps as noted in your tutorial for the new version? I am a complete newbie to GTM.

  9. AC says

    Hello Simo,

    thx for another agreat post!

    I’ve implemented ABR on my site (the easy method) and not only BR, but also my average page depth (depth of visit), drop drastically. Is that normal?

  10. says

    Hello Simo,

    Thanks for great article. I think you made a small misprint in your third variant.

    Did you mean :
    if (((didScroll) || (visitTookTime)) && !(bounceSent)) {
    Instead of:
    if ((didScroll) && (visitTookTime) && !(bounceSent)) {

    • says

      Hi Artem,

      Nope. I specifically want to send the event only when the user scrolled AND dwelled on the page for 30s. Your suggestion would send the event twice. First for didScroll === true and next for visitTookTime === true.


  11. says

    Simo, struggling a bit over here. This is what I did but I am unsure if this is at all ok:


    1. Name: Event DOM (is this a semantically correct name?)
    Chose event: Custom Event

    2. Fire on
    Event name: (? don’t know what to use here)

    Fire this trigger when all of the conditions are true:
    Event (dropdown) equals (dropdown) gtm.dom (free text)


    1. Choose Product: Custom HTML tag

    2. Configure tag: /insert script from your post above/

    3. Fire on: Event DOM (created in step a.))

    Any help appreciated ;)

      • says

        Thanks for the quick reply!

        When debugging and testing, Dwell and scroll tag should fire only after 30s AND after the user has scrolled?

        Mine is currently firing as soon as page is loaded which is no good. Need to test more I guess :)

      • says

        The “Dwell and Scroll” is the Custom HTML Tag which fires on gtm.dom. Then you need an Event Tag which fires upon a Custom Event Trigger where Event name is “GAEvent”. Nothing else is needed trigger-wise.

  12. says

    Warning – Longish post :)

    1st tag “Dwell and scroll” injects JS code which registers two “things” (scroll and timer) and declares a visit as non bounce.

    It should fire on ALL pages (right?) since we can’t know on which page user might land/enter site. Why do you suggest that I create a custom trigger called “pageview” with the settings:

    > event: pageview
    > trigger type: DOM ready
    > fire on: Page URL matches RegEx .*

    instead of just clicking ALL PAGES option whe nsetting up the tag?

    2nd tag is called “UA – Event – Generic” with the following settings:

    > Product: Google analytics
    > Tag type: Universal analytics
    > Tracking ID : {{ /* saved in a variable /* }}
    > track type: event
    > Category: {{ event category }}
    > Action: {{ event action }}
    > Label: {{ event label }}
    > Value: {{ event value }}

    This tag fires on custom created trigger called “GAEvent”:

    > type: Custom event
    > Fire on: GAEvent (I typed this manually into the field)

    Im not sure this second tag Works correctly and what is it’s purpose?

    1. user lands on site
    2. on the DomReady event trigger “pageview” triggers “Dwell and scroll” tag to fire
    3. custom JS code is executed

    IF scroll TRUE && IF timer > 30.000 ms => fire Tag and push object visitBounce=false into dataLayer

    4. firing of “Dwell and Scroll” tag somehow (?) triggers GAEvent trigger
    5. which in turn triggers “UA Event Generic” tag
    6. which pushes events’ category, action, label and value into the dataLayer

    so that the Google Analytics service can know WHICH event actually occured, not just that this visit should not be counted as a “bounced visit”. Am I correct? :)

    • says

      Hey man,

      Hah, I should really start rewriting these older articles to be clearer (and GTM V2 compliant).

      Anyway, I use gtm.dom as the rule because that means the timer doesn’t start until the DOM has been rendered. So I don’t expect the user to do any major interaction with the site until the HTML has loaded, which is why I don’t want to start the timer any sooner.

      The Event Tag is the one that sends the hit to GA. It fires on GAEvent, because when the “Dwell and Scroll” custom HTML detects a dwell time and that the user has scrolled, it pushes a dataLayer object with ‘event’ : ‘GAEvent’ and ‘eventCategory’ : ‘noBounce’, and the ‘event’ : ‘GAEvent’ fires the Event Tag (because that’s the Custom Trigger event name).

  13. says

    Simo – I GOT IT UP AND RUNNING! ;)

    Yes, you might consider rewriting some of this stuff, especially for us newbs. So, all in all:

    – you use “pageview” trigger because it waits for all elements of the DOM to load; “All pages” fires immediately after GTM code is loaded which is near the beginning of the page source
    – if >30.000 ms && scroll = true => fires the “Dwell and scroll” tag
    – this tag pushes object into the dataLayer
    – {event : ‘GAEvent’} triggers the GAEvent trigger which in turn triggers the generic “UA Event tag”
    – … which finally sends the event data to the GA servers

    Voila! Thanks for the patience man, with your help (and another fella Googler) we’re progressing by leaps and bounds.

    Kiitos paljon!

  14. matt says


    I am going off these steps and have some questions:

    1) Create new Custom Event Trigger with Event name: GAEvent
    name = GAEvent
    Event = Custom Event
    Fire On = GAEvent (manually typed in)
    2) Create new Data Layer Variable with variable name: eventCategory
    3) Create new Data Layer Variable with variable name: eventAction
    4) Create new Event Tag that fires with the trigger from (1) and uses the variables from (2) and (3) in the relevant fields (Event Category and Event Action)
    Name = UA Event Generic
    Product = Google Analytics
    Tag Type = Universal Analytics
    Tracking ID = site tag ID found in GA
    Track Type = Event
    Category = eventCategory – select created in step 2
    Action = eventAction – select created in step 3
    Fire On = GAEvent – tag created in step 1
    5) Create new Page View Trigger with “DOM Ready” as the Trigger Type
    Name = Event DOM
    Choose Event = Page View
    Configure Trigger = DOM Ready
    Fire On = All Page Views
    6) Create new Custom HTML Tag with the JavaScript code from the article, and have it fire on the trigger from (5).
    Name = Dwell and Scroll
    Choose Product = Custom HTML Tag
    Configure Tag = enter script
    Fire On = Event DOM – select trigger created in step 5

    Does this look right? When testing I see Dwell and Scroll fires immediately and UA Event Generic fires after 30 seconds but does not fire before the 30 seconds if scrolling.


Leave a Reply

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

Please do not write HTML or other formatted code in your comments!