Google Consent Mode with Piwik PRO Consent & Tag Manager

Hello,

We’re using Piwik PRO Consent & Tag Managers, and we are running ads with Google Ads. We’re using the built-in Google Ads conversion & remarketing tags.

As Google announced the release of its new Consent Mode, it’s clear that this is will soon be mandatory for advertisers to use it (by March 2024): Google announces Consent Mode v2 - What does it mean?.

I don’t think Piwik PRO is a certified Google CMP partner: https://cmppartnerprogram.withgoogle.com/. How can I implement Google Consent Mode using Piwik PRO Consent & Tag Managers?

Thank you very much!

5 Likes

Update
I used Google’s documentation: Administrar la configuración de consentimiento (Web)  |  Security and Privacy hub  |  Google for Developers.

By following gtag.js method, you should be able to install Consent Mode with Piwik PRO Tag & Consent Managers. Make sure you trigger the tags in the correct order: default values first.

You can verify your implementation following these instructions: Manage consent settings (web)  |  Security and Privacy hub  |  Google for Developers.
I personally used the Network tab & Google Tag Assistant to check my setup.

I hope this will help!

Hello @Thomas_Anode_FR
Did you have a look at this :

Hello @romainproximity,

I did, but I think that following Google’s guidelines is better. The main reason is that it clearly invites you to set default values to “denied” first. It is not the case in this Piwik PRO’s tutorial: it’s only update commands.

Additionally, Google’s guidelines integrate the new parameters “ad_user_data” and “ad_personalization”. I think Piwik PRO documentation would benefit from a little update. :slight_smile:

1 Like

Hey everyone, sorry for not responding earlier - we are working on an update :saluting_face:.

3 Likes
1 Like

Hi @anthonybartczak ,
The first step to integrate Google Consent Mode v2, with all the denied default.
It must be on the webpages code, or can it be a tag in Tag Manager ?
Thanks for your help,

Hi @christophe.henaff,

In my opinion (and Google’s documentation mentions this method) it’s best to embed the defaults directly in your website, before any tag managment or tracking tool is loaded. It’s the safest way because it skips potential issues with 3rd party tools.

I think any TMS should be capable of setting Consent defaults, even Piwik’s.

And maybe I misunderstand how Piwik’s Consent Manager works, but Google consent mode v2 integration | Piwik PRO help center mentions we should update the Consent Manager defaults when the Event is stg.consentDecisionMade.

But doesn’t that apply only to the rare cases when somebody first gives their consent (or even rarer, when they update consent)?

=> If we only use that stg.consentDecisionMade Event, there will be no Consent given for any Pageviews where there was no interaction with the Consent Manager (=99% of Pageviews)…

The documentation is missing the crucial info that the update command needs to be fired on every pageview (or that the Consent “default” settings need to reflect the user’s current consent state on every page).

Last question:
When I try to get {{ Consents }} via a Custom HTML Tag, it always refers to window.sevenTag.getVariableValue('Consents', 3). This 3 refers to t.variables[3][“Consents”] which sometimes contains a filled Consents object, but also sometimes not…
image

E.g. here (trying to receive “Consents” with the stg.consentDecisionMade Event: Consents.current_state is just an empty object → setting Consent Mode via the googleConsentModeMapping function from the documentation fails (everything gets “denied”…

image

Why does a reference to a Variable not always get the latest version of it? (=which would contain the correct values)
image

Looking at this again, it seems like PP TM, when 'eval’ing the Custom HTML Tag (with all comments, yikes, performance anyone?!), injects the INDEX of the data layer event that triggered the current tag into the window.sevenTag.getVariableValue('Consents', INDEX);call.

So if a tag has multiple triggers (like stg.pageView OR stg.consentDecisionMade, the value counts that was in the data layer when the tag was first called…

So if stg.pageView is the 3rd event (INDEX = 3) and stg.consentDecisionMade the 22nd (INDEX = 22), Piwik TM will still get the data of the Consents variable as it existed when the 3rd event happened…

So when somebody updates their consent level, this will never make it to the Google Consent Mode update as it will “update” it based on data layer INDEX 3 again (when Consent still did not exist)…

That is totally not how I expect a Variable reference to work. In GTM, it always takes the last value… I will have to re-visit quite some code now that I found this out…

I will replace Variable reference by this manual call now which always gets the latest version of a Variable:

window.sevenTag.getVariableValue('Consents', dataLayer.length);

But this should not be necessary…

It’s not about ability, it’s just recommended. See my answer here:

And also the page view event
image

“It’s not about ability, it’s just recommended.”
=> I understand that it is only your recommendation, but I think a TMS is precisely there to avoid such stuff having to go on a website, so it is weird to get such a recommendation from a TMS vendor…

“And also the page view event”
=> What I wrote is: If I use both pageview and consentDecisionMade, the content of the Consents variable that is evaluated in the Custom HTML tag is always based on the first event on the page that triggered the tag (=the pageview, when we did not have consent yet).

I’m not sure what you mean here. Edit: let me have a look again.

image
image

@loldenburg do you mean changing the consent decision after the initial one was given?

This is what finally works now (took me 4 hours to build sth that should take 30 minutes):

One Custom HTML Tag, fired upon pageView and stg.consentDecisionMade:

<script>
    // fired this upon stg.pageView and stg.consentDecisionMade
    window.gtag = window.gtag || function(){dataLayer.push(arguments)};
    /*
    Validate: Check Google Ads Tag and look for these parameters:
    - gcs: G110 => Ads granted, Analytics denied
    - gcd: 13r3q3r3r5 => denied by default and granted after update, except analytics storage
     */
    if (!window.googleConsentModeMapping) { // initialize the function only once per page
        function googleConsentModeMapping(consents) {
            var consentLevels = window.sevenTag.getVariableValue('Consents', dataLayer.length-1);
            TMSHelper.console("Consent Levels: ", consentLevels);
            if (consentLevels) {
                var consObj = {};
                Object.keys(consents).forEach(function(consent) {
                    consObj[consent] = (consentLevels.current_state[consents[consent]] === 1) ? 'granted' : 'denied';
                });
                gtag('consent', 'update', consObj);
            }
        }
        // set default consent values for the page, they are directly updated afterwards and when Consents are given or change
        gtag('consent', 'default', {
            'ad_storage': 'denied',
            'ad_user_data': 'denied',
            'ad_personalization': 'denied',
            'analytics_storage': 'denied'
        });
    }

    googleConsentModeMapping({
            "ad_storage": "custom_consent", // "custom_consent" is the value  which we also use to fire Google Ads Pixels
            "ad_user_data": "custom_consent",
            "ad_personalization": "custom_consent",
            "analytics_storage": "denied" // we don't use Google Analytics, so we don't need to give analytics storage rights
    });
</script>

I needed to use
window.sevenTag.getVariableValue('Consents', dataLayer.length-1);,
otherwise the stg.consentDecisionMade Event never leads to an actual “update” of the google consent levels because it always refers to window.sevenTag.getVariableValue('Consents', 3); => 3 the INDEX of the pageView Event which first fired the Tag…

Maybe I’m missing something here but I don’t think that is true. As an example, see the deanonymization tag:
image

The consent decision event I’ve sent before shows the same thing.

So which part I’m missing :sweat_smile:?

Ok solved this (thx).
The problem arose from

  1. Defining the googleConsentModeMappingfunction in a way that it does not get redefined every time the Tag is triggered (by checking for !window.googleConsentModeMapping before defining it).
  2. Checking for the {{ Consents }} variable INSIDE that function.

If we write {{ Consents }} in a tag, Piwik TM turns this into window.sevenTag.getVariableValue('Consents', INDEX); => the INDEX refers to the index of the dataLayer of the Event which first fired the Tag… So if “pageView” triggered the Tag and “pageView” was the 3rd Event, the index will be 3 => window.sevenTag.getVariableValue('Consents', 3);

Now if this is INSIDE the function, the function will use the value of the 3rd data layer event (pageView) even if the function is called later, e.g. on stg.consentDecisionMade. => Thus any future update of the tag will result in a reset to the Consent values at the state of the pageView => my problem.

So in short, I would have expected Piwik TM to not hard-write the value 3 into a function but instead use a dynamic function that always gets the latest variable value at runtime (like in GTM).

Anyway, the solution is:

  • to take the {{ Consents }} out of the function
  • add a another argument consentLevels to the function
  • put {{ Consents }} into the function call at the end

=> This seems to be the best compromise. We are not re-defining the function every time the Tag is called and we are still getting always the latest value.

<script>
    // fired on stg.pageView and stg.consentDecisionMade
    window.gtag = window.gtag || function(){dataLayer.push(arguments)};
    /*
    Validate: Check Google Ads Tag and look for these parameters:
    - gcs: G110 => Ads granted, Analytics denied
    - gcd: 13r3q3r3r5 => denied by default and granted after update, except analytics storage
     */
    if (!window.googleConsentModeMapping) { // initialize the function only once per page
        function googleConsentModeMapping(consents, consentLevels) {
            console.log("Consent Levels: ", consentLevels);
            if (consentLevels) {
                var consObj = {};
                Object.keys(consents).forEach(function(consent) {
                    consObj[consent] = (consentLevels.current_state[consents[consent]] === 1) ? 'granted' : 'denied';
                });
                gtag('consent', 'update', consObj);
            }
        }
        // set default consent values for the page, they are directly updated afterwards and when Consents are given or change
        gtag('consent', 'default', {
            'ad_storage': 'denied',
            'ad_user_data': 'denied',
            'ad_personalization': 'denied',
            'analytics_storage': 'denied'
        });
    }

    googleConsentModeMapping({
            "ad_storage": "custom_consent", // "custom_consent" is the value for "external media" which we also use to fire Google Ads Pixels
            "ad_user_data": "custom_consent",
            "ad_personalization": "custom_consent",
            "analytics_storage": "denied" // we don't use Google Analytics, so we don't need to give analytics storage rights
    }, {{ Consents }}); // the "Consents" variable needs to remain outside of the function because otherwise it will always contain the consent levels of when the tag was first called.
</script>