Hubspot Cookie Consent with Google Tag Manager

Hubspot is an all-encompassing solution for marketers. You can handle in one place all your marketing needs, from emails to pixels to ad spend tracking.

But as with all-encompassing solutions, you get a mediocre experience at best. Your ad spend tracking can include only Google, Facebook and LinkedIn Ads. Other platforms aren’t welcome. Your pixels can easily be loaded on the site, but custom events can’t really be handled via Hubspot.

Don’t get me wrong, I’m still a Hubspot fan. But I acknowledge that it can’t handle everything marketing at the level I expect it to do out of the box.

One example for this is marketing pixels.

Working with Hubspot for years now, my best practice has always been to handle all the pixels separately from the Hubspot tag and directly via GTM. It improves the site’s performance and gives me granular control over tags and triggers.

The Hubspot Cookie Banner

A similar issue occurs when handling cookie consent using the Hubspot cookie banner. When loading the Google Tag Manager or Google Analytics scripts (or any other script for that matter) via the Hubspot tag, you get a simple integration out of the box with the consent banner.

Consent granted? GTM & GA will run. No consent? no code will run.

This simplistic solution blocks off any third-party code (e.g. chat widget) you have in GTM. It also ignores the built in Consent Mode that Google offers for differential tracking based on the user’s consent.

So the alternative is to have GTM implemented standalone on the page, and have it inherit the consent state from Hubspot’s cookie banner. Simple, right?

Hubspot’s cookie banner has multiple options for integration, that differ based on the regulation of various global regions, i.e. the consent required in the EU is different than the one required in the US.

The standard EU (opt-out by default) is easy to set up. When the consent banner loads, it saves a cookie of the name __hs_cookie_cat_pref. This cookie takes the values of the consent categories granted (or denied) as boolean values 1:true_2:true_3:true.

Opt out by default and show a consent banner

This consent state can be read by GTM using a small JS that extracts the value of the consent categories from the cookie. This is great for the Default Consent event. It can also track any subsequent changes to the consent state (e.g. a user approving tracking) to trigger a new Consent Update tag. So far so good.

In a certain client’s case, I was also asked to add a default state for users outside of the EU that consents to all tracking. The setup in Hubspot is again very simple.

Allow all cookies by default w/o showing a banner

The tricky part about this setup was that this doesn’t load any banner and doesn’t save these values to the same cookie. So we can’t use these values for the Default Consent tag.

Let’s get practical

Default Consent tag

The first tag we’ll create is the Default Consent tag which uses Simo Ahava’s Consent Mode Custom Template.

The consent command should be set to Default and the trigger is Consent Initialization.

The consent settings should read from the Hubspot cookie set by the banner. In absence of such a cookie, it will default to ‘denied’.

Each variable here is a Custom JS, fetching the specific value from the cookie.

function() {

var cookieValue = document.cookie.match('(^|;)\\s*' + '__hs_cookie_cat_pref' + '\\s*=\\s*([^;]+)');
  if (cookieValue) {
    var consentString = cookieValue.pop();
    var consents = {};
    consentString.split('_').forEach(function(item) {
      var parts = item.split(':');
      var key = parts[0].trim();
      var value = parts[1].trim();
      if (value === 'true') {
        consents[key] = true;
      } else if (value === 'false') {
        consents[key] = false;
      }
    });
  
      var analytics     =  consents['1'] || false;
      var advertisement =  consents['2'] || false;
      var functionality =  consents['3'] || false;

      return (analytics);  
  
  } else {
    return (false);
  }

}

A final important touch to the variable is to convert true/false values to granted/denied using the Format value option.

Consent state listener

Now we need to track changes in the consent state, so that we can update the tags on page.

This requires a Custom HTML tag that loads on every page.

This tag register to consent change events and when such happens, triggers a Data Layer event with the changes and saves the changes to a cookie.

<script>
var _hsp = (window._hsp = window._hsp || []);
  
_hsp.push(["addPrivacyConsentListener", function (consent) {
    dataLayer.push({
      'event': 'consent_change',
       ad_storage: consent.categories.advertisement ? "granted" : "denied",
       analytics_storage: consent.categories.analytics ? "granted" : "denied",
       personalization_storage: "denied",
       functionality_storage: consent.categories.functionality ? "granted" : "denied",
       security_storage: "granted"
    });    
      // Check if the cookie already exists
      if (!getCookie("__hs_cookie_cat_pref")) {
        // Save 180 days cookie
        var cookieValue = '1:' + (consent.categories.analytics ? 'true' : 'false') + '_2:' + (consent.categories.advertisement ? 'true' : 'false') + '_3:' + (consent.categories.functionality ? 'true' : 'false');
        var expiryDate = new Date();
        expiryDate.setDate(expiryDate.getDate() + 180); // 180 days from now
        document.cookie = '__hs_cookie_cat_pref=' + cookieValue + '; expires=' + expiryDate.toUTCString() + '; path=/; domain=.domain.com';
      }

}]); // hsp listner
  function getCookie(name) {
  var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
  if (match) return match[2];
}
 </script>

Make sure you replace .domain.com in the script with your own domain

Consent Update tag

This tag, using the same Custom Tag Template for Consent Mode, triggers on the consent_change event initiated by the Custom HTML above.

Unlike the Default tag, it sets the categories consent state based on the Data Layer Variables sent with the event. It can potentially feed on the same cookie, but for sake of efficiency data layer was chosen.

I also set the Push dataLayer Event to checked, so that this issues a trigger for pageview pixels that were restricted from firing up until the consent changed (to avoid losing UTM and Click ID data etc.)

The end game looking something like this:

Leave a Reply

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

Related Posts