Shopify pixels, Shopify Consent Banner and Google consent mode V2

Here is a topic that I’ve been navigating on for a while now and came up with a solution that works for now but want to check with other brilliant minds here and potentially improve on it.

There are many 3rd party cookie consent banner apps out there that claim to have nailed this in my experience these are false claims or requires merchants to spend even more time setting everything up properly. Bottom line is: 3rd party consent apps do not offer an out of the box solution for this problem.
At the end of the day I am all about using native Shopify tools as much as possible.

Problem

The native GA integration via Google and Youtube sales channel + the Shopify consent banner do not work with Google consent Mode v2.

The first issue is that the GA (from native google sales channel) is using cookie, so naturally the Shopify consent banner will block cookies prior to user interacting with the Shopify consent banner. Which in effect means that the first page load is never tracked. When users decide to interact with the consent banner there are a few options:

  • User accepts all tracking (G111)
  • User declines all tracking (G100)
  • User accepts some tracking (G101 or G110)

With native GA and Shopify consent banner - none of the above get the G*** parameter assigned - which is the problem.

Solution

The solution is to move the merchant to GTM or course. Why? - you might ask
Well to start with, GTM does not use cookies and we can load GTM container without user consent.

Great, but now we have another set of problems.
By default, Shopify pixel does not sent the G*** parameters to GTM so we need Shopify pixel to handle that. Thanks to Shopify customerPrivacy API we can rightfully do that in Shopify pixel where we listen from events coming from Shopify consent banner and then push events to GTM with the selected consent from the banner, while assigning the correct G*** tag.

Furthermore, we can send the G*** tag to GTM on the initial page load (before customers interact with the banner) - and yes, this is legit as we do only need to send the G111 parameter with a page view event so it complies with GDPR. It basically works the same as a click event. You don’t need a consent from customers for the click events, right?

After users interact with the consent banner and choose their preference, we then update the G*** tag based on their preference and send that to GTM, basically solving the following issues:

  • Track customers first interaction with the site by emitting an empty page_view event to GTM
  • Update the consent status right after they interact with the consent banner(not on the next page load)
  • Correctly assign the consent and G*** parameter for GTM base don user selection
  • Stay Shopify native and not use a 3rd party app

The above work like a charm even though it was a bumpy road to piece everything together.
I have ran this with the legal teams of a couple of merchants and checked with the marketing teams of even more merchants and they are all onboard with the solution.
However, my questions are:

  1. Is anyone handling this in a more holistic way?
  2. Currently there is a lot of discrepancy between customerPrivacy API and google (listed below) - are there any plans to have the API be more aligned with Google’s consent mode v2? (we have Microsoft now that onboarded the same framework too)
  3. Anyone who is serious about their business would most likely want a setup like this. However, in reality, this setup takes about 8-12 hours to implement and test. A lot of merchants do not have availability to such dev resources so they will be exposed to GDPR claims.

For reference here is the Shopify customerPrivacy api uses:

{
     "analyticsProcessingAllowed": boolean,
     "marketingAllowed": boolean,
     "preferencesProcessingAllowed": boolean,
     "saleOfDataAllowed": boolean,
 }

And here is what the Google Consent mode V2 uses

{
  'ad_storage': 'denied',
  'ad_user_data': 'denied',
  'ad_personalization': 'denied',
  'analytics_storage': 'denied'
}

Below are a couple of useful resources that i have used to puzzle everything together.

1 Like

The Apps Developed by the end-party (google as an example) do code their pixels to relay the proper consent.

Otherwise you could create a web pixel that takes the shopify values and send them as a gtag version something like…

// Subscribe to customer privacy consent collected events to update the status
api.customerPrivacy.subscribe('visitorConsentCollected', (event) => {
    customerPrivacyStatus = event.customerPrivacy;
    // send gtag update
});
// PAGE VIEWED EVENT
analytics.subscribe('page_viewed', (event) => {
    // Prepare payload with event name and customer privacy status
    const payload = {
        eventName: event.name,
        customerPrivacyStatus: customerPrivacyStatus,
    };
    console.log("consent_state: ", payload);
    // send gtag value
});
1 Like

Thanks for engaging with the post.

The issue is the end-party app only supports GA integration, not GTM.

On a basic level what you described works (as per shopify documenation) but that is not enough. In order to send the correct privacy parameters from Shopify Pixel to GTM it requires additional logic and mapping, for consent to work in GTM - hence my drive for posting this.

You see, the issue is that the customerPrivacyStatus object looks something like this:

 {
  "analyticsProcessingAllowed": boolean,
  "marketingAllowed": boolean,
  "preferencesProcessingAllowed": boolean,
  "saleOfDataAllowed": boolean,
}

However as mentioned in the original post, GTM expects something else.

agreed - i’m saying you’d have to get the shopify status, map it, and update google

{
     "analyticsProcessingAllowed": boolean, // = analytics_storage
     "marketingAllowed": boolean, // = ad_storage
     "preferencesProcessingAllowed": boolean, // = ad_personalization
     "saleOfDataAllowed": boolean, // = ad_user_data
 }

pass that mapped status to:

gtag('consent', 'default', {
  'ad_storage': 'denied', // mapped status
  'ad_user_data': 'denied', // mapped status
  'ad_personalization': 'denied', // mapped status
  'analytics_storage': 'denied' // mapped status
});

or when changed

 gtag('consent', 'update', {
  'ad_storage': 'denied', // mapped status
  'ad_user_data': 'denied', // mapped status
  'ad_personalization': 'denied', // mapped status
  'analytics_storage': 'denied' // mapped status
});

If I understand correctly, the solution would be to do this?

window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments) }
let customerPrivacyStatus = init.customerPrivacy;

const initializeGtmTag = (w, d, s, l, i) => {
  w[l] = w[l] || [];
  w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
  const f = d.getElementsByTagName(s)[0];
  const j = d.createElement(s);
  const dl = l !== 'dataLayer' ? '&l=' + l : '';
  j.async = true;
  j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
  f.parentNode.insertBefore(j, f);
};

initializeGtmTag(window, document, 'script', 'dataLayer', 'GTM-XXXXXXX');

api.customerPrivacy.subscribe('visitorConsentCollected', (event) => {
  customerPrivacyStatus = event.customerPrivacy;

  const updatedConsent = {
    ad_storage: customerPrivacyStatus?.marketingAllowed ? 'granted' : 'denied',
    analytics_storage: customerPrivacyStatus?.analyticsProcessingAllowed ? 'granted' : 'denied',
    ad_user_data: customerPrivacyStatus?.saleOfDataAllowed ? 'granted' : 'denied',
    ad_personalization: customerPrivacyStatus?.preferencesProcessingAllowed ? 'granted' : 'denied',
  };

  gtag('consent', 'update', updatedConsent);

  var styles = ['color: white', 'background: black'].join(';');
  console.log('%c%s', styles, 'Consent mode V2 updated: ', payload);
})

gtag('consent', 'default', {
  ad_storage: 'denied',
  analytics_storage: 'denied',
  ad_user_data: 'denied',
  ad_personalization: 'denied',
  wait_for_update: 500
});

gtag('consent', 'update', {
  ad_storage: customerPrivacyStatus?.marketingAllowed ? 'granted' : 'denied',
  analytics_storage: customerPrivacyStatus?.analyticsProcessingAllowed ? 'granted' : 'denied',
  ad_user_data: customerPrivacyStatus?.saleOfDataAllowed ? 'granted' : 'denied',
  ad_personalization: customerPrivacyStatus?.preferencesProcessingAllowed ? 'granted' : 'denied',
});

yes that would work - set default, look for initialize (what was set previously in shopify), watch for updates

yes, eventually this is pretty much what i came up with as well, after a lot of digging.

With an addition of sending an empty page_view event after initializing the GTM tag, otherwise the first page visit is lost as consent event happens after the first page_view event triggers.

Would be good if this would be standardised across multiple vendors so that the same values are used, instead of one using boolean, others a string etc

Hi Octavian,

I hope you don’t mind me jumping into this thread, but your response caught my attention.

We are experiencing a significant amount of bot traffic from users with screen resolutions of 800x600 and 1024x768. To address this, we have used JavaScript to detect these uncommon resolutions and block the GA4 pixel from firing.

I am wondering if it is possible to similarly prevent the Shopify Analytics pixel from firing for these resolutions. I understand that Shopify Analytics might be server-side, but I am exploring potential solutions:

  1. Deleting the Shopify Analytics object on the client side using JavaScript when these resolutions are detected.
  2. Setting privacy or content restrictions, such as a “no track” object, when these resolutions are detected.

Could you please provide some guidance on this?

Thank you for your help.

Wanted to discuss the mapping here, based on the different platforms.

Shopify Consent

  • Preferences: Cookies that remember customer preferences, such as country or language, to personalize visits to the website.
  • Analytics: Cookies to understand how customers interact with the site.
  • Marketing: Cookies to provide ads and marketing communications based on customer interests.

Google Consent

Consent Type Description
ad_storage Enables storage, such as cookies (web) or device identifiers (apps), related to advertising.
ad_user_data Sets consent for sending user data to Google for online advertising purposes.
ad_personalization Sets consent for personalized advertising.
analytics_storage Enables storage, such as cookies (web) or device identifiers (apps), related to analytics, for example, visit duration.
functionality_storage Enables storage that supports the functionality of the website or app, for example, language settings
personalization_storage Enables storage related to personalization, for example, video recommendations
security_storage Enables storage related to security such as authentication functionality, fraud prevention, and other user protection

I feel like mapping should be like this (against standard shopify consent)

  • Preferences - functionality_storage & personalization_storage
  • Analytics - analytics_storage
  • Marketing - ad_storage & ad_user_data & ad_personalization

notes:
I think security_storage probably sits under essential/necessary
I also think that analytics_storage is ok to GA4 if that is the end of analytics (ie: not feeding into Google Ads)

Setup Wise
RE: consent setup, we actually did this in reverse. so we have a google consent V2 pop up from iubenda (and associated setup), then once consent is logged in the GTM we send custom HTML to update the appropriate with checks on consent.

then when using custom pixels, we call the Shopify consent in the custom pixel to control the mapping.

1 Like

also worth noting that you can call on these once set via

  • userCanBeTracked: Analytics and marketing both must be accepted (when required)
  • userDataCanBeSold: Sale of data must not be rejected

in console, via the following:

window.Shopify.customerPrivacy.userCanBeTracked()
window.Shopify.customerPrivacy. userDataCanBeSold()
window.Shopify.customerPrivacy.saleOfDataRegion();

userDataCanBeSold is an opt out process, which is only applicable to CCPA and VCDPA. as its an opt out process, you would need to have an opt out page for this for those regions. recommendations are to remove it from consent completely (as per shopify).

you can call the region via

window.Shopify.customerPrivacy.saleOfDataRegion();

1 Like

Hi Symediane, thanks for your code which seems really on point !

I have a question though, where should I put this code on? On a custom pixel? On the theme liquid files?

Thanks for you help