How to display or block personalized Adsense ads based on consent for IAB TCF v2.0 with Quantcast Choice?

I’ve chosen Quantcast Choice as the consent management platform for my website. However with their basic solution Google Adsense ads are still served to visitors before they give their consent for the related vendor (Google). Since 2 days there’s a notice in Adsense confirming this issue and after a grace period of 90 days no ads will be served anymore. The error message: "2.1a: the Tag or SDK isn’t receiving a TC string due to CMP status being stub, loading, or error."

I’m not familiar at all with scripts, but it seems I have to use some to make Quantcast Choice actually work.
Basically I need to know how to:

  • not serve any Google ads before a visitor consent to Google
  • serve personalized ads after consent
  • serve non-personalized ads after no consent

What I’ve learned so far:

Before a visitor’s consent, I can add this script to my Google Adsense code to not show any ads:

<script>(adsbygoogle=window.adsbygoogle||[]).pauseAdRequests=1;</script>

No problem so far. However then I should be able to know if a visitor has given consent for the vendor Google. Google’s vendor ID in the TCF v2.0 is 755. I found the following mixture of code and text on one of the Quantcast pages, but I don’t know how to use this. Should I put this on my webpages within the Quantcast Choice script or …? (source linked below)

{{QC - __cmpConsents.iabVendorConsentIds}} matches the regular expression (^|,)755(,|$).

So if ID 755 is found, I should call:

(adsbygoogle=window.adsbygoogle||[]).pauseAdRequests=0

and if ID 755 is not found, I should call:

(adsbygoogle=window.adsbygoogle||[]).requestNonPersonalizedAds=1
(adsbygoogle=window.adsbygoogle||[]).pauseAdRequests=0

But how do I do these "calls"?

So I think I understand the steps to follow, it’s just that I don’t know how to implement these steps and connect the dots. Any help would be much appreciated!

Sources I’ve used:

Example of a website that seems to do what I want to achieve: carscoops.com

9 thoughts on “How to display or block personalized Adsense ads based on consent for IAB TCF v2.0 with Quantcast Choice?”

  1. IMO, if you want to follow official instructions for Quantcast Choice and Google Adsense, this is the way:

    Add all of this in your <head> tag:

    google adsense

    <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
    <script>
    //pause all ad requests until further notice
    (adsbygoogle=window.adsbygoogle||[]).pauseAdRequests=1;
    (adsbygoogle=window.adsbygoogle||[]).push({
        google_ad_client: "ca-pub-XXXXXXXXXXXXXXXX",
        enable_page_level_ads: true
    });
    </script>
    

    quantcast choice CMT for TCF 2.0

    <script>
    (function() {
    var host = window.location.hostname;
    var element = document.createElement('script');
    var firstScript = document.getElementsByTagName('script')[0];
    var url = 'https://quantcast.mgr.consensu.org'
    .concat('/choice/', YOUR_QUANTCAST_ID_HERE, '/', host, '/choice.js')
    var uspTries = 0;
    var uspTriesLimit = 3;
    element.async = true;
    element.type = 'text/javascript';
    element.src = url;
    firstScript.parentNode.insertBefore(element, firstScript);
    function makeStub() {
        var TCF_LOCATOR_NAME = '__tcfapiLocator';
        var queue = [];
        var win = window;
        var cmpFrame;
        function addFrame() {
            var doc = win.document;
            var otherCMP = !!(win.frames[TCF_LOCATOR_NAME]);
            if(!otherCMP) {
                if(doc.body) {
                    var iframe = doc.createElement('iframe');
                    iframe.style.cssText = 'display:none';
                    iframe.name = TCF_LOCATOR_NAME;
                    doc.body.appendChild(iframe);
                } else {
                    setTimeout(addFrame, 5);
                }
            }
            return !otherCMP;
        }
        function tcfAPIHandler() {
            var gdprApplies;
            var args = arguments;
            if(!args.length) {
                return queue;
            } else if(args[0] === 'setGdprApplies') {
                if(
                args.length > 3 &&
                args[2] === 2 &&
                typeof args[3] === 'boolean'
                ) {
                    gdprApplies = args[3];
                    if(typeof args[2] === 'function') {
                        args[2]('set', true);
                    }
                }
            } else if(args[0] === 'ping') {
                var retr = {
                    gdprApplies: gdprApplies,
                    cmpLoaded: false,
                    cmpStatus: 'stub'
                };
                if(typeof args[2] === 'function') {
                    args[2](retr);
                }
            } else {
                queue.push(args);
            }
        }
        function postMessageEventHandler(event) {
            var msgIsString = typeof event.data === 'string';
            var json = {};
            try {
                if(msgIsString) {
                    json = JSON.parse(event.data);
                } else {
                    json = event.data;
                }
            } catch (ignore) {}
            var payload = json.__tcfapiCall;
            if(payload) {
                window.__tcfapi(
                payload.command,
                payload.version,
                function(retValue, success) {
                    var returnMsg = {
                        __tcfapiReturn: {
                        returnValue: retValue,
                        success: success,
                        callId: payload.callId
                        }
                    };
                    if(msgIsString) {
                        returnMsg = JSON.stringify(returnMsg);
                    }
                    event.source.postMessage(returnMsg, '*');
                },
                payload.parameter
                );
            }
        }
        while (win) {
            try {
                if(win.frames[TCF_LOCATOR_NAME]) {
                    cmpFrame = win;
                    break;
                }
            } catch (ignore) {}
            if(win === window.top) {
                break;
            }
            win = win.parent;
        }
        if(!cmpFrame) {
            addFrame();
            win.__tcfapi = tcfAPIHandler;
            win.addEventListener('message', postMessageEventHandler, false);
        }
    };
    makeStub();
    var uspStubFunction = function() {
        var arg = arguments;
        if(typeof window.__uspapi !== uspStubFunction) {
            setTimeout(function() {
            if(typeof window.__uspapi !== 'undefined') {
                window.__uspapi.apply(window.__uspapi, arg);
            }
            }, 500);
        }
    };
    var checkIfUspIsReady = function() {
        uspTries++;
        if(window.__uspapi === uspStubFunction && uspTries < uspTriesLimit) {
            console.warn('USP is not accessible');
        } else {
            clearInterval(uspInterval);
        }
    };
    if(typeof window.__uspapi === 'undefined') {
        window.__uspapi = uspStubFunction;
        var uspInterval = setInterval(checkIfUspIsReady, 6000);
    }
    })();
    </script>
    

    TCF 2.0 API listener

    <script>
    window.__tcfapi('addEventListener', 2, function(tcData, listenerSuccess) {
        if(listenerSuccess) {
            //check the eventstatus
            if(tcData.eventStatus === 'useractioncomplete' || tcData.eventStatus === 'tcloaded') {
                if(!tcData.gdprApplies) {
                    //GDPR does not apply to this user, load ads immediately
                    (adsbygoogle=window.adsbygoogle||[]).pauseAdRequests=0;
                    return;
                }
    
                //deal with parsing tcData for IAB Vendor consents
                //deal with personalized/non-personalized Google ads
                if((tcData.vendor.consents[755] || tcData.vendor.legitimateInterests[755]) && (tcData.purpose.consents[1] && tcData.purpose.consents[3] && tcData.purpose.consents[4] && tcData.purpose.legitimateInterests[2] && tcData.purpose.legitimateInterests[7] && tcData.purpose.legitimateInterests[9] && tcData.purpose.legitimateInterests[10])) {
                    //consent signals sufficient for personalized ads
                    //set personalized ads and unpause loading process
                    (adsbygoogle=window.adsbygoogle||[]).requestNonPersonalizedAds=0;
                } else if((tcData.vendor.consents[755] || tcData.vendor.legitimateInterests[755]) && (tcData.purpose.consents[1] && tcData.purpose.legitimateInterests[2] && tcData.purpose.legitimateInterests[7] && tcData.purpose.legitimateInterests[9] && tcData.purpose.legitimateInterests[10])) {
                    //consent signals NOT sufficient for personalized ads
                    //set non-personalized ads and unpause loading process
                    (adsbygoogle=window.adsbygoogle||[]).requestNonPersonalizedAds=1;
                }
                (adsbygoogle=window.adsbygoogle||[]).pauseAdRequests=0;
            }
        }
    });
    </script>
    
    Reply
  2. Just to help everyone understand, the 2.1a Google IAB TCF v2 Error is likely caused by Google Advertise Product Tags being added to the website before a user consent has been set/established. This is a short video shows the expected behavior, ads not loading until after a user has given consent. If you see ads loading in the background before a user has selected their consent then you are getting 2.1a errors because you are adding Google tags without waiting for consent.

    The solution to this will vary depending upon how you are adding Google Advertising Product tags to your site but hopefully the below information and Adsense example help.

    Google Tag Manager

    If you are using Google Tag Manager to add Choice and Google tags to your site you can use the guide here https://help.quantcast.com/hc/en-us/articles/360051794434 and https://help.quantcast.com/hc/en-us/articles/360051794434-TCF-v2-GTM-Implementation-Guide-IAB-Vendor-Tag-Blocking as references.

    Adsense Specific Example

    Taking the example from https://support.google.com/adsense/answer/9042142 I believe this is how you would need to rework the example to wait for proper consent signals from Quantcast Choice TCF v2.0.

    I have not had a chance to fully test this so please let me know if you have any issues with the code and I will update the example.

    SEE BELOW THIS CODE BLOCK FOR AN UPDATED VERIONS

    <html>
      <head>
        <title>Your site title</title>
      </head>
      <body>
    
        <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
        <script>
          // Initially pause adsbygoogle (wait for consent to unpause)
          (adsbygoogle=window.adsbygoogle||[]).pauseAdRequests=1;
        </script>
    
        <script>
          ( function() {
            // Run this in an interval (every 0.1s) just in case we are still waiting for consent
            var cnt = 0;
            var consentSetInterval = setInterval(function(){
              cnt += 1;
    
              // Bail if we have not gotten a consent response after 60 seconds.
              if( cnt === 600 )
                clearInterval(consentSetInterval);
    
              if( typeof window.__tcfapi !== 'undefined' ) { // Check if window.__tcfapi has been set
                clearInterval( consentSetInterval );
    
                window.__tcfapi( 'addEventListener', 2, function( tcData,listenerSuccess ) {
                  if ( listenerSuccess ) {
                    if( tcData.eventStatus === 'tcloaded' || tcData.eventStatus === 'useractioncomplete' ) {
                      if ( ! tcData.gdprApplies ) {
    
                        // GDPR DOES NOT APPLY, UnpauseAdRequests
    
                        // Set request non-personalized ads to false as GDPR does not apply.
                        (adsbygoogle=window.adsbygoogle||[]).requestNonPersonalizedAds=0;
    
                        // Unpause ads, as GDPR does not apply.
                        (adsbygoogle = window.adsbygoogle || []).pauseAdRequests=0;
    
                      }
                      else {
    
                        // GDPR DOES APPLY
    
                        // Purpose 1 refers to the storage and/or access of information on a device.
                        var hasDeviceStorageAndAccessConsent = tcData.purpose.consents[1] || false;
    
                        // Google Requires Consent for Purpose 1
                        if (hasDeviceStorageAndAccessConsent) {
                          // GLOBAL VENDOR LIST - https://iabeurope.eu/vendor-list-tcf-v2-0/
                          // CHECK FOR GOOGLE ADVERTISING PRODUCTS CONSENT. (IAB Vendor ID 755)
                          var hasGoogleAdvertisingProductsConsent = tcData.vendor.consents[755] || false;
    
                          // Check if the user gave Google Advertising Products consent (iab vendor 755)
                          if(hasGoogleAdvertisingProductsConsent) {
                            var hasPersonalizedProfileConsent = tcData.purpose.consents[3] || false;
                            var hasPersonalizedAdsConsent = tcData.purpose.consents[4] || false;
    
                            // Check if have add personalization consent Purpose 3 and 4
                            if( hasPersonalizedAdsConsent && hasPersonalizedProfileConsent ) {
                              // Set request non-personalized ads to false.
                              (adsbygoogle=window.adsbygoogle||[]).requestNonPersonalizedAds=0;
                            }
                            else {
                              // Set request non-personalized ads to true.
                              (adsbygoogle=window.adsbygoogle||[]).requestNonPersonalizedAds=1;
                            }
    
                            // Unpause ads , the user has granted consent for purpose 1 and given google consent.
                            (adsbygoogle = window.adsbygoogle || []).pauseAdRequests=0;
                          }
                        }
                      }
                    }
                  }
                } );
              }
              cnt++;
            }, 100);
          })();
        </script>
    
        <!-- One test unit for GDPR -->
        <ins class="adsbygoogle"
             style="display:inline-block;width:970px;height:250px"
             data-ad-client="ca-pubxxx"
             data-ad-slot="slot_id">
        </ins>
    
        <!-- Another test unit for GDPR -->
        <ins class="adsbygoogle"
             style="display:inline-block;width:250px;height:250px"
             data-ad-client="ca-pubxxx"
             data-ad-slot="slot_id">
        </ins>
    
        <script>
          // This will trigger the ad request if ads were unpaused in the CMP consent check above.
          (adsbygoogle = window.adsbygoogle || []).push({});
        </script>
    
      </body>
    </html>
    

    Edit: New Updated Version Added (aug 18)


    This might be a better version than the above. This is again untested, so please test and give feedback if you are having issues. The main differences with this updated version is:

    1. We are no longer checking tcData.purpose.consents[3] and tcData.purpose.consents[4] instead we are relying on google to decide between showing personalized ads vs not.

    2. We do not add https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js to the page until we have consent, preventing any unwanted cookies from being added until we are sure we have consent to do so. This also allowed us to remove the (adsbygoogle=window.adsbygoogle||[]).pauseAdRequests=1; items as well

      <html>
        <head>
          <title>Your site title</title>
        </head>
        <body>
          <script>
            ( function() {
              var insertAdsByGoogleJs = function() {
                var element = document.createElement('script');
                var firstScript = document.getElementsByTagName('script')[0];
                var url = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js";
                element.async = true;
                element.type = 'text/javascript';
                element.src = url;
                firstScript.parentNode.insertBefore(element, firstScript);
              };
              // Run this in an interval (every 0.1s) just in case we are still waiting for consent
              var cnt = 0;
              var consentSetInterval = setInterval(function(){
                cnt += 1;
                // Bail if we have not gotten a consent response after 60 seconds.
                if( cnt === 600 )
                  clearInterval(consentSetInterval);
                if( typeof window.__tcfapi !== 'undefined' ) { // Check if window.__tcfapi has been set
                  clearInterval( consentSetInterval );
                  window.__tcfapi( 'addEventListener', 2, function( tcData,listenerSuccess ) {
                    if ( listenerSuccess ) {
                      if( tcData.eventStatus === 'tcloaded' || tcData.eventStatus === 'useractioncomplete' ) {
                        if ( ! tcData.gdprApplies ) {
                          // GDPR DOES NOT APPLY
                          // Insert adsbygoogle.js onto the page.
                          insertAdsByGoogleJs();
                        }
                        else {
                          // GDPR DOES APPLY
                          // Purpose 1 refers to the storage and/or access of information on a device.
                          var hasDeviceStorageAndAccessConsent = tcData.purpose.consents[1] || false;
                          // Google Requires Consent for Purpose 1
                          if (hasDeviceStorageAndAccessConsent) {
                            // GLOBAL VENDOR LIST - https://iabeurope.eu/vendor-list-tcf-v2-0/
                            // CHECK FOR GOOGLE ADVERTISING PRODUCTS CONSENT. (IAB Vendor ID 755)
                            var hasGoogleAdvertisingProductsConsent = tcData.vendor.consents[755] || false;
                            // Check if the user gave Google Advertising Products consent (iab vendor 755)
                            if(hasGoogleAdvertisingProductsConsent) {
                              // Insert adsbygoogle.js onto the page.
                              insertAdsByGoogleJs();
                            }
                          }
                        }
                      }
                    }
                  } );
                }
                cnt++;
              }, 100);
            })();
          </script>
          <!-- One test unit for GDPR -->
          <ins class="adsbygoogle"
               style="display:inline-block;width:970px;height:250px"
               data-ad-client="ca-pubxxx"
               data-ad-slot="slot_id">
          </ins>
          <!-- Another test unit for GDPR -->
          <ins class="adsbygoogle"
               style="display:inline-block;width:250px;height:250px"
               data-ad-client="ca-pubxxx"
               data-ad-slot="slot_id">
          </ins>
          <script>
            // This will trigger the ad request if ads were unpaused in the CMP consent check above.
            (adsbygoogle = window.adsbygoogle || []).push({});
          </script>
        </body>
      </html>
    
    Reply
  3. Thank’s for this snippet.

    There is a strange problem.
    On Chrome, when I put an Objection on Legitimate Interest Purpose(s) "Select basic ads", i still have ads when i reload the page.
    On firefox, ads are not showing.

    Do you have the same problem ? I can’t find how to solve it

    Reply
  4. I would recommend a much simple solution. With TCF 2.0 you don’t have to manually configure Adsense parameters (personalized ads etc.) based on a given consent. You just have to guarantee, you start loading ads after consent is successfully loaded. Adsense library read the consent strings automatically and display ads in accordance with a given consent.

    Example code for integration Quantcast Choices with Adsense:

    <!-- Quantcast Choice. Consent Manager Tag v2.0 (for TCF 2.0) -->
    ...
    <!-- End Quantcast Choice. Consent Manager Tag v2.0 (for TCF 2.0) -->
    
    <script>
    __tcfapi('addEventListener', 2, function(tcData, success) {
        if (success) {
            if (tcData.eventStatus == 'useractioncomplete' || tcData.eventStatus == 'tcloaded') {
                var hasStoreOnDeviceConsent = tcData.purpose.consents[1] || false;
    
                if (hasStoreOnDeviceConsent) {
                    var adsbygoogle_script = document.createElement('script');
                    adsbygoogle_script.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
                    document.head.appendChild(adsbygoogle_script);
                }
            }
        }
    });
    </script>
    
    Reply

Leave a Comment