Corné Dorrestijn

Getting Cookiebot to work with Partytown

Partytown is a great tool to sandbox third party scripts. But sometimes it can be tricky to get some scripts to work correctly. Some might fail completely, and some might have some quirks. But sometimes issues also arise when a script is not run within Partytown, but has to communicate with a script that is. Recently, I had this last issue when I was implementing Cookiebot on a website that had Partytown installed. I quickly figured out that running Cookiebot inside Partytown was not going to work, but running it outside Partytown also wasn't that straight forward.

What does not work?

Google Consent Mode is a way to manage consent in Google Tag Manager. Consent is changed using events in the dataLayer. Initially consent is set to denied, and after denying, or (partly) accepting cookies, it will send an update event. The update event contains which groups are granted, and which groups are denied. The website had GTM running inside Partytown, with dataLayer events forwarded. This seemed to work right, but the consent update event never seemed to reach Google Tag Manager. I followed the Forwarding Events And Triggers guide from the Partytown website, and everything seemed right. Manually pushing events using the console also didn't cause any issues, but tags that required consent to be set did not trigger. To help debug this process I added a few tags that would do a simple console log, and would only trigger with different consent settings. Debugging using tag assistant does not work out of the box, this is however possible. If you are interested in doing this, take a look at these two GitHub repo's by slawekkolodziej and superside-oss.

Cookiebot would push 4 events when changing consent. Depending on the granted scopes it would send the events: cookie_consent_preferences, cookie_consent_statistics, cookie_consent_marketing and the consent mode event ['consent', 'update', { .. }] followed by a cookie_consent_update event. Only the consent update event did not get pushed. 

After a few hours of debugging, I think that one of these two are probably the culprit. This script that gets the dataLayer from window:

this.getGTMDataLayer = function() {
    return null == window[this.dataLayerName] && (window[this.dataLayerName] = []),
    Array.isArray(window[this.dataLayerName]) ? window[this.dataLayerName] : []
}

or, this script that pushes the event, and especially the helper function that mimics gtag:

this.signalGoogleConsentAPI = function(googleConsentModeDisabled, dataRedactionMode, consentPreferences, consentStatistics, consentMarketing) {
    googleConsentModeDisabled || (this.pushGoogleConsent("consent", "update", {
        ad_storage: consentMarketing ? "granted" : "denied",
        ad_user_data: consentMarketing ? "granted" : "denied",
        ad_personalization: consentMarketing ? "granted" : "denied",
        analytics_storage: consentStatistics ? "granted" : "denied",
        functionality_storage: consentPreferences ? "granted" : "denied",
        personalization_storage: consentPreferences ? "granted" : "denied",
        security_storage: "granted"
    }),
    "dynamic" === dataRedactionMode && this.pushGoogleConsent("set", "ads_data_redaction", !consentMarketing),
    this.getGTMDataLayer().push({
        event: "cookie_consent_update"
    }))
}
,
this.pushGoogleConsent = function gtag() {
    this.getGTMDataLayer().push(arguments)
}

Somewhere here, the event is lost into oblivion, and never finishes in the dataLayer. However, this is in the Cookiebot script, and it is something I have no control over.

The solution

To fix this issue, I came up with a very simple script, that just mimics what Cookiebot does. When the CookiebotOnConsentReady event is triggered, I recreate the consent update events. The mapping is copied from the Cookiebot script, so they match. However, you can create a different mapping of course.

<script type="text/javascript">
  window.addEventListener(
    "CookiebotOnConsentReady",
    function () {
      var consent = window.Cookiebot.consent;
      gtag("consent", "update", {
        ad_storage: consent.marketing ? "granted" : "denied",
        ad_user_data: consent.marketing ? "granted" : "denied",
        ad_personalization: consent.marketing ? "granted" : "denied",
        analytics_storage: consent.statistics ? "granted" : "denied",
        functionality_storage: consent.preferences ? "granted" : "denied",
        personalization_storage: consent.preferences ? "granted" : "denied",
        security_storage: "granted",
      });
      gtag("set", "ads_data_redaction", !consent.marketing);
      dataLayer.push({ event: "cookie_consent_update" });
    }
  );
</script>

This seems to work fine, and might also be a solution to other compatibility issues between scripts running outside the sandbox, and scripts running inside the sandbox. The key here is that the functions are defined and called explicitly on window.