Full Stack Consent Management
For many businesses, consent management is a full stack problem. Consent preferences may need to be synchronized across different web applications, mobile applications, backend databases, and third party tools. Transcend's suite of products has you covered across all device types, and is built to scale with high website traffic, and many domains/applications. You can manage your consent policies through a single Admin Dashboard that lets you set the rules and monitor consent changes across your stack.
Full stack consent management architecture diagram with Transcend's Preference Store feature
Full stack consent management architecture diagram with your own backend store
When managing consent preferences, it is useful to think about two categories of users:
- Anonymous website visitors
- Authenticated users with a login session
Based on the law and your business objectives, you should consider building consent strategies for both types of users. You will want to create a strategy that determines how to reconcile consent preference when a user changes between anonymous and authenticated. We recommend starting with a strategy around anonymous users first, and then layering in a strategy for authenticated users. If you are planning on syncing consent preferences across multiple devices or websites, starting with anonymous website visitors allows you to narrow in on consent workflows for a single device at a time. Once you feel comfortable with your workflows on a single device, you can expand that model across multiple devices/services to create a unified experience.
The most noticeable privacy compliance violations are the ones that happen in plain sight. Anyone in the world can visit your website and check for some basic functionality:
- a) is there an interface that allows for the website visitor to change consent preferences if that visitor is in a region that has consent laws
- b) if the website visitor enables a browser signal, like the GPC signal, is that opt out respected instantly without the website visitor needing to manually opt out?
Both of these requirements can be met with a strategy focused on anonymous users only without any integration with your backend. Consent preferences can be set and enforced entirely on the client device.
The above diagram outlines the architecture for regulating consent preferences on a single device for an anonymous user. From a code perspective, the setup is as simple as it gets: add the script airgap.js script tag found in your "Developer Settings" to the head of your website. The script will be loaded directly from our CDN on to the client device. Whenever you make changes to your cookie or data flow categorizations, you can publish an updated version of your airgap.js script by clicking click "set changes live". All of your configuration options and classifications will be bundled into the airgap.js script and published to the CDN.
It is possible to self-host the airgap.js script on your own CDN. We generally do not recommend this as a solution as it requires additional setup, additional maintenance, and we feel strongly that we've built in world-class performance optimizations to our CDN.
The script will initialize and immediately begin intercepting cookies and data flows. There is no additional network request required to fetch additional configuration after the script is loaded. Consent preferences are stored in local storage directly on the customer's browser. If the user has previously been on your website on that browser, then the consent preferences will be restored from the prior session. If no explicit consent has been given, then the default consent preferences will be determined based on the regional policies you define in the Admin Dashboard.
If you have multiple websites using the same airgap.js script, the local storage consent preferences can be synchronized on the customer device using a built-in technology we call the Cross-Domain Interface, or XDI.
The consent preferences stored in local storage can be accessed using the airgap.getConsent()
API.
An example output looks as follows:
const data = { confirmed: true, purposes: { Advertising: true, Functional: true, SaleOfInfo: false, Analytics: true, }, timestamp: '2022-10-27T08:30:01.526Z', };
Breaking this down:
- purposes: a mapping between the type of opt out, and the status of that opt out: true | false | 'Auto'.
- The four purposes listed in the example above are the out-of-the-box purposes, however this object can be extended to have other custom purposes.
- Recommendation is to map Functional, Analytics, Advertising to GDPR/LGPD like laws and SaleOfInfo to CPRA and other US state laws with do not sell/share requirements.
- Advertising and SaleOfInfo are often overlapping
- The value for each purpose can be one of:
true
: The website visitor explicitly confirmed consent for that purpose.false
: The website visitor explicitly unconfirmed consent for that purpose, OR the website visitor was opted out by default based on your regional experiences configuration.'Auto'
: The website visitor is opted in by default based on your regional experiences configuration. The website visitor has never explicitly confirmed consent for that purpose.
- confirmed: true if the user explicitly confirmed those preferences, and false if preferences are being inferred based on defaults
- timestamp: the time at which consent was confirmed.
Additionally, the airgap.js script can detect privacy signals, using the airgap.getPrivacySignals()
API.
An example output looks as follows:
Set(2) { 'GPC', 'DNT' }
Breaking this down:
- GPC: the GPC signal is something users can set in their browser to signal that their data should not be sold.
- The CA government has given fines for failure to respect this signal.
- This signal is enabled in Brave & Firefox browsers, as well as using chrome extensions like DuckDuckGo Privacy Essentials for Chrome.
- We typically see between 1-5% of traffic carrying the GPC signal. The airgap.js script can calculate this rate on your website once you enable telemetry.
- DNT: the DNT signal is a setting that can be set in the browser to disable tracking.
- The CA government has not yet stated that this signal needs to be respected, however, if you interpret a consent law to give users the right to opt out of tracking and analytics, you should consider respecting this browser signal.
- We typically see 10-20% of traffic carrying the DNT signal. The airgap.js script can calculate this rate on your website once you enable telemetry.
The following are examples of airgap.getConsent()
for various states of browser signals:
The user consent map shown above is a representation of what consent would look like for an anonymous user who lands on the site for the first time and has not explicitly given a consent choice. confirmed:false
is set to indicate that the user’s consent purpose map is inferred from the Consent Experience configuration.
In this case, the user’s consent map shows confirmed: true
, meaning that the user has explicitly set their consent choice. The purposes
show SaleOfInfo: false
which tells us that the user elected to opt-out of the sale of their info explicitly. Here the timestamp reflects the time the user explicitly confirmed their consent choice.
When a site visitor with a GPC signal lands on the site for the first time, the consent manager will observe the GPC signal and treat it as an explicit consent choice by the user. As a result, consent is set to confirmed: true
and the purpose SaleOfInfo: false
. Note that some consent purposes like advertising
, analytics
and functional
are still set to true
. This is because GPC specifically covers the request to opt-out of Sale of Personal Info. As a result, users with a GPC signal will not be opted-out of additional purposes until/if they explicitly do so. You can use the timestamp here to track when the GPC signal was inferred.
When a site visitor with a DNT signal lands on the site for the first time, the consent manager will observe the signal and treat it as an explicit consent choice by the user. As a result, consent is set to confirmed: true
. All consent purposes are set to false here, as Do Not Track is commonly understood to apply to all tracking purposes. You can use the timestamp here to track when the DNT signal was inferred.
Users landing on the site for the first time with both DNT and GPC privacy signals are treated the same way as if they had only one browser signal: it’s treated as an explicit consent choice and confirmed: true
is set. All consent purposes are set to false.
This table summarizes the different scenarios and the resolved consent status of the user:
User opted in via UI interaction | User opted out via UI interaction | Unconfirmed Opted-In via default opt-out state | Unconfirmed Opted-Out via default opt-out state | |
---|---|---|---|---|
GPC Signal Detected | User stays opted in | User stays opted out | User becomes confirmed opted out for Sales of Personal Info | User becomes confirmed opted-out for Sales of Personal Info |
DNT Signal Detected | User stays opted in | User stays opted out | User becomes confirmed opted out | User becomes confirmed opted-out |
It is worth noting that default behavior is that manual UI interaction can opt a user back into tracking, even if they have the GPC/DNT signal enabled. However, the UI can be configured to prevent the user from being able to opt out while a browser signal is enabled if you so choose.
Understanding the different types of opt outs, the default values for those opt outs, and the interaction of different browser signals are the big takeaways for this phase. There is not a one-size fits all solution to consent management, but the following questions can be good to guide you through this first phase:
-
Consent purposes and opt-out rights
- What opt out rights should be enabled for users on your site?
- Do the default opt out types handle your use case, or do you need custom purposes?
- For each opt out purpose, which countries or states should have the ability to opt in or out of that purpose?
- When should a user be opted-out by default for a consent preference versus opted-in?
-
Consent interface
- What should the consent interface look like in a given region?
- Should the consent banner auto-prompt or be triggered by a button in the privacy policy or footer?
-
Locating site visitors: resolving regional information
- If you plan to bias your experience based on region, how do you want to resolve that regional information?
- By GeoIP? Browser languages? Browser timezones?
-
Browser privacy signals
- Do you plan to respect each browser signal?
- Are these respected universally or only in certain regions?
For both anonymous and authenticated users, consent preferences are set and enforced entirely on the client device with Transcend’s consent manager. However, under certain privacy laws like CPRA, it may be important to remember the consent preferences a known site visitor has explicitly given so they can be respected when the user is on other devices or browsers.
This can be accomplished by layering in a strategy for storing and re-storing consent choices for authenticated users into the existing strategy for anonymous users.
The diagram above shows the infrastructure incorporating a backend data storage system into the original consent strategy. The airgap.js script is loaded directly from Transcend’s CDN to the client device and consent choices are regulated client-side in the same way for authenticated users as anonymous users. However, for authenticated users, consent information can be pulled from local storage and sent to a backend data store, which in turn can be shared back across multiple devices. The high-level process is:
- Create a data model to store consent in your backend database
- Create an API endpoint to receive user consent and serve it back to logged-in users
- Send consent data to your backend for logged-in users
- Re-use session data to authenticate the user
- Pull consent data from local storage
- Send the network request to persist consent data in the background
- Restore consent data from the backend when user is logged in on multiple devices
In the next sections we’ll go into each of these in more detail.
You can choose to either use Transcend's Preference Store offering or build your own. Choosing between the two largely depends on whether you are comfortable with Transcend holding the information, and having a larger availability constraint on Transcend; if you host your own backend and Transcend goes down, your site will still function.
The first step is to decide where to store user consent data in your backend and then to create the data model. The user consent map below contains the data structure that should be modeled into the backend data store. This can be implemented as simply a set of boolean values to store the consent confirmed state and the true/false state for each purpose. We recommend storing the timestamp as well, to make reconciliation efforts easier. In addition, you can optionally store any browser signals that are detected for the user.
const data = { confirmed: true, purposes: { Advertising: true, Functional: true, SaleOfInfo: false, Analytics: true, }, timestamp: '2022-10-27T08:30:01.526Z', };
Once the data model is in place, you’ll need to create an endpoint to receive consent preferences from the device to your database, and persist them back to the user.
To set and retrieve consent preferences for a user, use the following APIs:
You will need to generate a JSON Web Token (JWT) on your server so that Transcend's SDKs can save an authenticated user's consent preferences when they sign in on your application. To do so, follow our guide on the Transcend Preference Store.
If you already built your own consent store, then you will need to integrate with airgap.js
to hydrate consent settings on load. To store and restore the user’s consent information in your own backend, follow our guide on persisting consent on your backend.
When an authenticated user is on the site, you can pull the consent data for the user from local storage and send the network request to your backend in the background. You can optionally pull the consent data only when the user has confirmed consent (given explicit consent) by referencing the confirmed
state, or you can store consent for logged-in users regardless of whether the consent choices were explicitly set or inferred.
For additional references and examples on how to pull consent data from local storage and use it in a network request to your backend, please see the guide on Persisting consent on your backend.
In order to persist and restore consent for a user on different browsers or cross-device, the user needs to be uniquely identified. We recommend reusing session data to authenticate the user to store consent preferences. This allows you to tie consent data to the correct user and restore it when they log in on a different device.
When a user lands on the site, the consent script will set a consent map for the user that reflects the default consent experience you have configured. If a known user authenticates on a new device or browser, you will need to reconcile consent between the backend data store and local storage to ensure the script is regulating network requests and trackers in-line with the consent choices the user previously set.
Check out the guide on Restoring consent from your backend for examples of how to dynamically add an onload
handler to reconcile user consent.
Reconciling consent differences will be a key part of setting up a full-stack consent solution. Common scenarios where user consent data may differ are outlined below, including recommendations for how to reconcile the differences.
At a high level, the scenarios you need to think about are:
Backend Consent: Opted In | Backend Consent: Opted Out | |
---|---|---|
Local Storage: confirmed opted-in | 😌 No reconciliation needed | 💥 Reconciliation needed |
Local Storage: confirmed opted-out | 💥 Reconciliation needed | 😌 No reconciliation needed |
Local Storage: unconfirmed opted-in | ✅ Become confirmed opted-in | 💥 Reconciliation needed |
Local Storage: unconfirmed opted-out | 💥️ Reconciliation needed | ✅ Become confirmed opted-out |
Where reconciliation is needed, there are different reconciliation methods you can consider:
- Always opting the user out when there is any discrepancy between backend & local storage
- Take the most recent consent timestamp
- Always restore the backend consent
You can decide to mix and match these different approaches for the different scenarios. For example, where local storage has an unconfirmed consent status, you can always restore the backend consent. However, where there is confirmed consent status in local storage, you can choose to take the most recent timestamp, or be conservative and always opt the user out.
Read on for more detail and to learn about the pros & cons of each approach.
If there is a difference between the opt-out preference between the client and backend data, (e.g. one says opted out and another says opted in), opt the user out.
- Pro: Reduces risk by taking a conservative approach
- Con: Has the potential to result in additional data loss
Another option to resolving consent data differences is to use the data that has the most recent, up to date timestamp. By comparing the timestamp of the consent preferences in the browser to the date stored in backend data, you can get an idea of which consent map most accurately reflects the user's current consent choice. You can look for confirmed:true
on data from the client to understand if the current consent on the browser was explicitly set by the customer.
- Pro: More likely to retain more data on users
- Con: Higher chance that user consent gets overwritten when they have previously opted-out
Another approach is to restore consent to the logged-in user regardless of what the client-side consent data is. In this approach, the backend data is treated as the source of truth regardless of whether the user's browser has updated consent at load time (eg: if a GPC signal is present on the new device but was not included in the stored consent obtained from a previous login/device).
- Pro: Login always restores the consent data stored in the backend source of truth
- Con: Failure to carry over an opt-out performed in anonymous session to logged-in session could be violation of law
This approach brings up additional questions to think through, like what is the expected behavior when a GPC signal is detected, but backend data is opted-out for all/additional purposes. Should the backend be updated to reflect the GPC signal?
Another situation you may want to consider when thinking through consent restoration options is the case where more than one user is using the same device. This could happen in public workstations where multiple users access the same the computer, shared mobile devices in a work environment, etc. In cases where you have a legal obligation to respect an opt-out request from an anonymous user who then authenticates, it can be tricky to know which anonymous consent choice belongs to which user, and which were carry-over from previous users in order to accurately store consent for the now-known users.
Consent information store on the client side should be compared to server side data during page load, and upon consent change.
<script> // Get values from the server side and compare them with the local consent function compareWithServer(localConsent) { // NOT INCLUDED: retrieve values from your server, compare, and reconcile the values // This example code just returns the same values as pass from local storage return { Functional: localConsent.purposes['Functional'], Advertising: localConsent.purposes['Advertising'], Analytics: localConsent.purposes['Analytics'], SaleOfInfo: localConsent.purposes['SaleOfInfo'], }; } function reconcileConsent(event) { // NOT INCLUDED: safely store authorization for later calls to get/setConsent. // See [Consent Authorization.](/_docs/consent-management/configuration/creating-your-own-ui.mdx#consent-authorization) setAirgapAuth(event); // Retreive consent as stored locally localConsent = window.airgap.getConsent(); // Implement this function to reconcile consent status, using fields from the consent object // https://docs.transcend.io/docs/consent-management/reference/api#trackingconsentdetails reconciledConsent = compareWithServer(localConsent); // Only update if something changed if ( localConsent.purposes['Functional'] != reconciledConsent['Functional'] || localConsent.purposes['Advertising'] != reconciledConsent['Advertising'] || localConsent.purposes['Analytics'] != reconciledConsent['Analytics'] || localConsent.purposes['SaleOfInfo'] != reconciledConsent['SaleOfInfo'] ) { // Set the local consent value // NOT INCLUDED: retrieve Authorization in getAirgapAuth(). In this case, the `event` object could directly be used instead. // See [Consent Authorization.](/_docs/consent-management/configuration/creating-your-own-ui.mdx#consent-authorization) window.airgap.setConsent(getAirgapAuth(), { Functional: reconciledConsent['Functional'], Advertising: reconciledConsent['Advertising'], Analytics: reconciledConsent['Analytics'], SaleOfInfo: reconciledConsent['SaleOfInfo'], }); } } </script> <script data-cfasync="false" src="https://cdn.transcend.io/cm/1021e4fb-97bd-49bc-8189-742ee76fee93/airgap.js" onload="reconcileConsent(event)" ></script>
This example is based on code from the previous example, notably compareWithServer(localConsent)
, as well as setAirgapAuth()
and getAirgapAuth()
(see Consent Authorization).
When consent is changed locally, you should compare with the server side value. In this case, this would likely lead to an update of the value store server side.
// Listen for consent change events airgap.addEventListener( 'consent-change', ({ detail: { consent, oldConsent, changes } }) => { reconcileConsent(getAirgapAuth()); }, );
Setting up the consent manager to regulate data flows & cookies client-side and implementing the ability to store and respect user consent goes a long way in creating a compliant consent program.
There may be cases where a user's consent choice should be persisted across third-party systems as well as stored on the client and internal databases. This may be something to consider if information that can be regulated under a consent privacy law is collected from users on the site and shared with third party systems for storage. Facebook Ads may be an example, where the client-side script appropriately blocks the network requests and cookies when a user opts-out, but you may want to additionally remove them from custom audiences you control in Facebook Ads.
You can use Transcend's DSR API to pass consent opt-out choices for a known-user to Transcend to be actioned across selected, integrated systems. In this way, you can effectively respect a user's opt-out choice on the client's side and across third party data you control. In practice, this allows you to create a DSR in conjunction with Transcend DSR Automation specific to the opt-out request and ensure that it's actioned across systems like Google Ads, Facebook Ads, Salesforce, Marketo, Mailchimp, etc. Additionally, you can use this process to opt users back in across third party systems if the user were to give consent at a later time.
To set this up, you can add an event listener to pull consent changes from local storage and use that to post a DSR request to Transcend. Check out the guide on Transcend's DSR API for details on implementing this call.
The following diagrams show the consent and privacy infrastructure when Transcend is hosting the infra, either with the Preference Store or your own backend store.
As an option, you can self-host the consent and privacy infrastructure in your own cloud. This is a good option for those who want an additional layer of security, and desire all data to be encrypted within their own cloud.