Consent Levels

Overview

It is ethical — and in many places, the law — to obtain consent from users when tracking their behavior on your website. And there can be different levels of consent.

For example:

  1. Strictly Necessary

  2. Analytics

  3. Personalization

  4. Marketing

A user might consent to, or reject, any combination of these levels.

I was heavily involved in the coding of this logic on our website. We created one data element for each consent level, then applied the appropriate data element to each Adobe Launch rule as a condition.

Here’s how the programming works.

OneTrust

OneTrust is a third-party application that we installed on our site. When the user first lands, a OneTrust banner or popup prompts them to select their tracking preferences. Once they do, OneTrust populates the following properties in the data layer with the corresponding Booleans:

dataLayer: {
  user: {
    consent: {
      C0001,
      C0002,
      C0003,
      C0004,
    },
  },
}

For example, if a user allowed Analytics tracking (Level 2), that property in the data layer would be set to:

dataLayer.user.consent.C0002: true

And if a user did not allow Marketing (Level 4) tracking:

dataLayer.user.consent.C0004: false

These preferences are stored in a browser cookie so that the same user doesn’t need to keep making these selections every time they return to our site.

Consent Level 1: Strictly Necessary

Ironically, the consent level 1 | strictly necessary data element is going to seem unnecessary from a programming perspective, because all it contains is:

return true;

It doesn’t even check the data layer — it just returns a hardcoded Boolean true.

Explanation

We do it this way because Level 1 is always allowed; the user cannot turn it off. Adobe Launch rules that fall into this category are not for Analytics, Personalization, or Marketing. They are for functionality, such as triggering slide-ins or popups. And we don’t want that functionality to break if there is a timing issue or glitch in the data layer, so we just hardcode it to true.

It might also seem unnecessary to bother adding the consent level 1 | strictly necessary data element as a condition in the applicable Launch rules if it is always going to be true. But we humans don’t want to keep thinking we forgot to add a consent level condition to those rules every time we open them, so we do ourselves a psychological favor and just add it.

Consent Level 2: Analytics

Because consent can have some pretty strict legal repercussions, our data elements don’t simply pull values straight from the data layer — they do a few custom validations first.

The consent level 2 | analytics data element uses the following custom code:

let consentLevel2 = false; // Default.
if (window.dataLayer?.user?.consent?.C0002 === true) {
  consentLevel2 = true;
}
return consentLevel2;

Explanation

  • Line 1: Default to false. We assume no tracking is allowed until proven otherwise. (What if the user rejected Analytics tracking, but a technical glitch caused our data layer not to load? If we defaulted to true, our tracking could illegally fire.)

  • Line 2: Check the C0002 value in the data layer.

    • NOTE: Optional chaining will throw warnings in Launch because, as of this writing, Launch can only handle up to ES6, and optional chaining is a feature of ES11. Although the code will still run in most browsers, Launch will flag each line of code containing optional chaining. To avoid this distraction, we use a long string of !!window.dataLayer && !!window.dataLayer.user etc. in real life. But for the purposes of this case study, I’m shortening it to the optional chaining syntax.

    • Note that we check the value and type (=== true). This safeguards against a type glitch causing a string like false to return truthy.

  • Line 3: If this value was true in the data layer, no sense pulling it again — it’s shorter to just hardcode it to true at this point.

  • Line 5: Return the result, whether it is still the default of false, or it has been overwritten to true.

Consent Level 3: Personalization

The main example of Personalization is Adobe Target. We use Adobe Target for A/B tests, and to tailor the site’s contents to known users. For example, if we know that a user was viewing a specific product the last time they visited our website, we can show that product to them when they return, and hopefully reignite that interest.

We’re doing the same validation check we did in the consent level 2 | analytics data element, plus we only allow Consent Level 3 to fire if Consent Level 2 was also true.

In other words, if the user declined Analytics tracking (Level 2), but allowed Personalization (Level 3), we still don’t fire Personalization.

Here is the code for the consent level 3 | personalization data element:

let consentLevel3 = false; // Default.
if (
  _satellite.getVar('consent level 2 | analytics') === true &&
  window.dataLayer?.user?.consent?.C0003 === true
) {
  consentLevel3 = true;
}
return consentLevel3;

Consent Level 4: Marketing

The consent level 4 | marketing data element is for marketing pixels. (We may place ads on Facebook, Google, Pinterest, etc. If those ads drive traffic to the site, we want to track that success.)

Once again, we only allow Marketing (Level 4) to fire if Analytics (Level 2) is also true. One reason for this is that if there is a question about the metrics from a marketing pixel, we want to be able to match it to the metrics from Adobe Analytics to see how they compare.

let consentLevel4 = false; // Default.
if (
  _satellite.getVar('consent level 2 | analytics') === true &&
  window.dataLayer?.user?.consent?.C0004 === true &&
  _satellite.getVar('consent | sharing allowed') === true &&
  !_satellite.getVar('consent | global privacy control')
) {
  consentLevel4 = true;
}
return consentLevel4;

Explanation

  • Lines 3 - 4: These should look familiar by now.

  • Lines 5 - 6: But marketing pixels share data with third parties. Therefore, we have two more conditions that must be met before we return true:

    • Line 5: The user must be allowing sharing. (More on this in the next section.)

    • Line 6: The user’s browser’s Global Privacy Control must not be active. (More on this in the section after that.)

Sharing Allowed

If a user has created an account on our website, they have the ability to save their sharing preferences. And if they do not want us to share their data with third parties, dataLayer.user.consent.sharingAllowed will be set to false.

Our consent | sharing allowed data element looks to that property, but it also contains custom validation to ensure that it is the proper data type. (When we send this value to AEP, it must be the expected data type, or we will have batch ingestion failures.)

let sharingAllowed = false; // Default.
if (window.dataLayer?.user?.consent?.sharingAllowed === true) {
  sharingAllowed = true;
}
return sharingAllowed;

Global Privacy Control

A full explanation of Global Privacy Control can be found here:

https://developer.mozilla.org/en-US/docs/Web/API/Navigator/globalPrivacyControl

For the purposes of this case study, suffice it to say that our consent | global privacy control data element just needs to return the value of Navigator.globalPrivacyControl. (It will be true if the user does not want their data shared with third parties.)

return Navigator.globalPrivacyControl;

Adobe Launch Rule Conditions

Once each data element was programmed and tested, we simply added the appropriate one as a condition to each Launch rule according to the level of consent required to fire that rule.

For example, if a Launch rule fired a marketing pixel, we added the consent level 4 | marketing data element as one of that rule’s conditions.

And if a Launch rule fired some Adobe Analytics tracking, we added the consent level 2 | analytics data element as one of that rule’s conditions.