Prebid.js: TCF 2.0 Tech Spec

Created on 30 Jan 2020  Â·  18Comments  Â·  Source: prebid/Prebid.js

Type of issue

Intent to implement.
Status: Open for comment.

Table of Contents

Overview

This technical specification explains the implementation of TCF 2.0 in Prebid.js. IAB's TCF 2 is coming in April 2020. TCF 1.1 is going to be fully deprecated after April. Prebid will update consent management module to support both the versions.

Prebid api

Prebid js api will remain same for Publishers. Consent management module will first try to use v2 and then fallback to v1 if not found.

pbjs.setConfig({
  consentManagement: {
    gdpr: {
      cmpApi: 'iab',
      timeout: 3000,
      allowAuctionWithoutConsent: false
    }
  }
});

Prebid will add new property apiVersion to the gdprConsent object. This will help bidder adapters in parsing the consent string and reading vendor data

gdprConsent: {
  consentString: 'BOKAVy4OKAVy4ABAB8AAAAAZ+A==',
  consentRequired: true,
  vendorData: <vendor data>,
  apiVersion: 2
}

TCF 2.0 API

  • Prebid consent management module will always look for __tcfapi first (TCF 2) and then fallback to __cmp (TCF 1) if __tcfapi is not found
  • Every consent manager will provide the following API function: __tcfapi(command, version, callback, parameter). The function __tcfapi must always be a function and cannot be any other type, even if only temporarily on initialization – the API must be able to handle calls at all times.
  • As per IAB spec for TCF 2.0, CMPs are required to support four API commands: 'getTCData', 'ping', 'addEventListener' and 'removeEventListener'

    • getTCData - The callback shall be called immediately and without any asynchronous logic with whatever is available in the current state of the CMP.
    // Sample call
    __tcfapi('getTCData', 2, (tcData, success) => {
      if(success) {
        // do something with tcData
      } else {
        // do something else
      }
    }, [1,2,3]);
    
    • ping - The ping command invokes the callback immediately without any asynchronous logic and returns a PingReturn object for determining whether or not the main CMP script has loaded yet and whether GDPR applies
    // Sample call
    __tcfapi('ping', 2, (pingReturn) => {
      // do something with pingReturn
    });
    
    • addEventListener - Registers a callback function with a CMP. The callback will be invoked with the TCData object as an argument whenever the TC String is changed and a new one is available. The eventStatus property of the TCData object shall be one of the following:

    | eventStatus | Description |
    |--------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
    | tcloaded | This shall be the value for the eventStatus property of the TCData object when a CMP is loaded and is prepared to surface a TC String to any calling scripts on the page. A CMP is only prepared to surface a TC String for this eventStatus if an existing, valid TC String is available to the CMP and it is not intending to surface the UI. If, however, the CMP will surface the UI because of an invalid TC String (e.g. it is too old, incorrect or does not reflect all the information the CMP needs to gather from the user) then an event with this eventStatus must not be triggered. |
    | cmpuishown | This shall be the value for the eventStatus property of the TCData object any time the UI is surfaced or re-surfaced, a TC String is available and has rendered "Transparency" in accordance with the TCF Policy. The CMP shall create a TC string with all the surfaced vendors’ legitimate interest signals set to true and all the consent signals set to false. If previous TC signals are present a CMP may also merge those into the now-available TC String in accordance with the policy. |
    | useractioncomplete | This shall be the value for the eventStatus property of the TCData object whenever a user has confirmed or re-confirmed their choices in accordance with TCF Policy and a CMP is prepared to respond to any calling scripts with the corresponding TC String. |

    // Sample call
    const callback = (tcData, success) => {
      if(success && tcData.eventStatus === 'tcloaded') {
        // do something with tcData.tcString
        // remove the ourself to not get called more than once
        __tcfapi('removeEventListener', 2, (success) => {
          if(success) {
            // oh good...
          }
        }, callback);
      } else {
        // do something else
      }
    }
    
    window.__tcfapi('addEventListener', 2, callback);
    
    • removeEventListener - The callback shall be called with false as the argument for the success parameter if the listener could not be removed (e.g. the parameter callback is not registered or is invalid).

Workflow

Below diagram and use cases details the general approach to be followed by consent management module to get tcString.

Diagram

Screen Shot 2020-01-29 at 10 27 43 PM

Use case 1: CMP is on same page

  1. Check if window__tcfapi is available in current frame or top window
  2. Use addEventListener api command to register callback function with a CMP. The callback will be invoked with the TCData object as an argument whenever the TC String is changed and a new one is available.
  3. Check the eventStatus property of TCData when callback executes
  4. If eventStatus is tcloaded, Prebid will remove the callback using removeEventListener api command, clear the timer and get the tcstring.
  5. If eventStatus is cmpuishown, Prebid will first check if tcstring is available and if purposeOneTreatment is set to true then it will try to get the tcstring and exit.
  6. If eventStatus is useractioncomplete, Prebid will clear the timer and get the tcstring.

    Use case 2: CMP is in iframe

  7. In this case prebid will not be able to find window.__tcfapi as CMP is in an iframe or top window.

  8. Prebid will try to locate iframe named __tcfapiLocator in the parent frame. Publishers must load the CMP in a parent (or ancestor) of all iframes that may need to establish a GDPR legal basis. At this step, Prebid also try to locate frame named __cmplocator as backup
  9. Register a callback to listen to dispatched messages from cmp iframe
  10. Send the postmessage with the addEventListener command to cmp iframe found in step 2.
{
  __tcfapiCall: {
    command: "<tcf api command>",
    parameter: <arguments>,
    version: <version>,
    callId: <random id to match postmessage request response>
  }
}
  1. On receiving message, callback registered will process the data. event.data shall follow the format given below.
{
  __tcfapiReturn: {
   returnValue: <TCData object>,
   success: boolean,
   callId: <unique id from the request>
  }
}
  1. Check the eventStatus property of TCData when callback executes
  2. If eventStatus is tcloaded, Prebid will remove the callback using removeEventListener api command, clear the timer and get the tcstring.
  3. If eventStatus is cmpuishown, Prebid will first check if tcstring is available and if purposeOneTreatment is set to true then it will try to get the tcstring and exit.
  4. If eventStatus is useractioncomplete, Prebid will clear the timer and get the tcstring.

    Resources

Open Questions

intent to implement stale

Most helpful comment

@epouradier The gdprApplies and consentString fields for bidder adapters will remain the same with the TCF2 updates, so if you're only reading those fields and don't need to send a different signal to your system (ie to signify its TCF2) - you should likely be fine.

If you're reading the vendorData field, you will likely need to make some updates, as the inner fields/properties will change with TCF2. I did a pass-through in PBJS to see which adapters would likely be impacted and noted them in the PR description (#4911).

All 18 comments

Hey @jaiminpanchal27

Thanks for putting this together. I had a few questions/items I wanted to confirm:

  • for the new api_version field in the gdpr_consent object, will we want to update the logic to specify 1 for anyone using the v1 CMPs?
  • if we get the tcstring through the tcloaded or useractioncomplete events, we do not need to check for the purposeOneTreatment in the string before accepting it?
  • when we register a listener and they provide a response, should we only expect a single response with one of those event types based on the user's current situation? Or should we expect multiple events to trigger in sequence as the user goes through the process; eg we see cmpuishown and then later useractioncomplete?
  • is there any special safeframe workflow/interaction that we should use with TCF2 (there was a recommended setup for the v1 spec)?

Hi @jaiminpanchal27

To add on @jsnellbaker questions

  • what happens if purposeOneTreatement is not set to true or is not present?
  • once we collect the consent string, is there a case where the consent string might change again, ie should we keep listening to any change that might happen again while on the same page?

thanks

@jsnellbaker

  1. before (or in addition to) adding the _eventListener_, shouldn't we call the API with getTCData to see if the eventStatus is already tcloaded or useractioncomplete, meaning that the consent data is already in a state that's ready to be used? The concern is that the _TC String_ may not change and therefore never fire an event. don't need to do this because:

Note: The addEventListener callback shall be immediately called upon registration with the current TC data, even if the CMP status is loading and the CMP has incomplete TC Data, so that the calling script may have access to its registered listenerId. Furthermore, on every TC String change the callback shall be called unless it is removed via removeEventListener.

  1. when in an iframe, may first want to look for __tcfapi() in parent windows (in case we're in a "friendly" iframe), since that is more direct than dealing with postMessage() or the SafeFrame interface.
  2. when searching parent iframes, we should also look for TCF v1 (__cmp/__cmpLocator) as a backup if TCF v2 can not be found, like we do in the current window.
  3. we should support a "static" cmpApi:
    consentManagement: { gdpr: { cmpApi: 'static', consentData: { getTCData: { tcString: 'base64url-encoded TC string with segments', gdprApplies: true } } } }

for #4 - static API. Wasn't this considered a bad pattern because then any following code/creative dropped would expect to read the consent data but would not find it in the page? IE single source of truth is not there..? @harpere

@mkendall07 - The use case for the static API as I understand it is pubs that cache consent data in first party cookies. If they detect that the user has already answered the CMP, they don't even load the CMP for subsequent pages. Adding @patmmccann for comment.

@harpere - there's a difference here with the way static data is handled for TCF1.1 From http://prebid.org/dev-docs/modules/consentManagement.html

consentManagement: {
            gdpr: {
              cmpApi: 'static',
              allowAuctionWithoutConsent: false,
              consentData: {
                getConsentData: {
                  'gdprApplies': true,
                  'hasGlobalScope': false,
                  'consentData': 'BOOgjO9OOgjO9APABAENAi-AAAAWd7_______9____7_9uz_Gv_r_ff_3nW0739P1A_r_Oz_rm_-zzV44_lpQQRCEA'
                },
                getVendorConsents: {
                  'metadata': 'BOOgjO9OOgjO9APABAENAi-AAAAWd7_______9____7_9uz_Gv_r_ff_3nW0739P1A_r_Oz_rm_-zzV44_lpQQRCEA',
                ...
                }
              }
            }
          }

Is the difference on purpose?

@jaiminpanchal27 - is the proposal that the bidrequest interface is different for TCF2?

Currently adapters read:

  • bidderRequest.gdprConsent.gdprApplies
  • bidderRequest.gdprConsent.consentString

It appears this spec defines different locations:

  • bidderRequest.gdpr_consent.consent_required
  • bidderRequest.gdpr_consent.consent_string
  • bidderRequest.gdpr_consent.api_version

Is this on purpose?

@jaiminpanchal27 can we keep the interface the same as of TCF1? then no changes will be required by bidders otherwise all bidders will need to update their code.

I think it would ideal to implement a version field into the bidderRequest.gdprConsent object. Part of the changes with the spec and with the updates to the module relate to the vendorData object.

In the v1, this value was pulled from the CMP through a dedicated API call named getVendorConsents. In v2, (I imagine) this value would be tcfData object that comes back from the events, as that object contains the breakdown of the various purpose/vendor consent settings.

These two objects that we get from the CMPs are structured differently and (I believe) contain unique fields. Since we have several adapters reading these values from the vendorData object to control parts of their logic, they'll need to know when the object is in v1 or v2 (so they can look at the appropriate fields for that version).

Also - it has been requested that if a _consent string_ changes while a user is on a page, we should use the updated _consent string_. So, should we remove the eventListener after we receive an eventStatus of tcloaded or useractioncomplete, or should we continue to listen and update the _consent string_ if it changes?

@jaiminpanchal27 - is the proposal that the bidrequest interface is different for TCF2?

Currently adapters read:

  • bidderRequest.gdprConsent.gdprApplies
  • bidderRequest.gdprConsent.consentString

It appears this spec defines different locations:

  • bidderRequest.gdpr_consent.consent_required
  • bidderRequest.gdpr_consent.consent_string
  • bidderRequest.gdpr_consent.api_version

Is this on purpose?

@bretg @pm-harshad-mane That was by mistake. I updated it.

@harpere Thanks Eric for reviewing,
2) Updated both use cases when trying to find window.__tcfapi. We should also look at top window to find tcfapi.
3) Updated the use case to find v1 as backup.

Also updated to not remove listeners as consent string may get updated.

in regard to the "static" question by @bretg - this is consistent. the format is: consentManagement.gdpr.consentData.<command>.<paramname>

with TCF 1.1 we have:

  • command = "getConsentData"
  • paramname = "consentData"

with TCF 2.0 we have:

  • command = "getTCData"
  • paramname = "tcString"

I don't believe this layer of code needs to check Purpose 1 or PurposeOneTreatment.

My understanding is that the reading the consent string can be done without Purpose 1 consent. Will confirm this.

@jaiminpanchal27 Can you confirm the bidders don't have to update their adaptors to fetch the signals gdprApplies and consentString as they remain the same?

Can you confirm the bidders don't have to update their adaptors to fetch the signals gdprApplies and consentString as they remain the same?

We are waiting until the change is in Master.

@epouradier The gdprApplies and consentString fields for bidder adapters will remain the same with the TCF2 updates, so if you're only reading those fields and don't need to send a different signal to your system (ie to signify its TCF2) - you should likely be fine.

If you're reading the vendorData field, you will likely need to make some updates, as the inner fields/properties will change with TCF2. I did a pass-through in PBJS to see which adapters would likely be impacted and noted them in the PR description (#4911).

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Was this page helpful?
0 / 5 - 0 ratings