8

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

Stan
  • 937
  • 5
  • 15
  • 33

3 Answers3

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>
quick
  • 1,104
  • 10
  • 19
  • This seems to be working. Ads are fired only after consent is granted. I just wrapped the eventListener call in an IIFE to make sure it registers at all times. – whidev Oct 04 '20 at 09:27
  • I updated the solution and load Adsense tag only if user agrees with Store and/or access information on a device (Purpose 1). Because "If you do not have consent for Google for Purpose 1 (Store and/or access information on a device), you should not call Google’s ad tag.". See https://support.google.com/adsense/answer/9804260?hl=en – quick Oct 07 '20 at 15:00
  • @quick Thanks for the update. I see why pauseAdRequests might not be needed anymore with your last edit. But please note you still have to pass the `data-ad-client` attribute to the script somehow. – DrLightman Oct 14 '20 at 07:15
  • I've done this but the ads still load while the consent dialog is waiting for user interaction. What am i missing? i've removed the from the tag however each ad in the page has this script before. Should i remove it? – Reznik Oct 19 '20 at 17:01
  • @DrLightman for the data-ad-client you should do script.dataset.adClient = "ca-pub-xxxxxxxxxxxxxxxxx"; – Reznik Oct 19 '20 at 17:08
  • @Reznik The issue was the before every ad. They have to be removed. – Reznik Oct 19 '20 at 21:36
  • On my site I am using this code after <! - End Quantcast Choice. Consent Manager Tag v2.0 (for TCF 2.0) -> But the Quancast banner is only shown to EU and UK users If I remove the code //pagead2.googlesyndication.com / pagead / js / adsbygoogle.js I only show ads to people who have seen and approved the use of cookies. How would I have to do to show ads when the consent banner is not shown in the rest of the countries – Pyaw Nov 10 '20 at 00:04
  • Great answer @quick (should be accepted). It's probably useful to mention it's using the IAB's CMP API v2.0, documented here: https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md – SuN Nov 11 '20 at 13:19
3

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>
Ryan Baron
  • 31
  • 2
  • It seems to be working. I tested the updated version on two sites already. The only problem is if I click DISAGREE and then call for options again and enable them all back, the ads are not shown anyway. Althought I don't think any of my visitors will go that far ... Thanks the code, I would never be able to come up with it myself. – elkah Aug 25 '20 at 21:29
  • @elkah I believe the issue you are referring to will be resolved with the Choice v12 release later this week. (You will NOT have to modify this/your code in any way). After the Quantcast Choice v12 release, can you re-test and let us know if the issue was resolved for you? [Choice Release Notes](https://help.quantcast.com/hc/en-us/articles/360047357574-Quantcast-Choice-Code-Release-Notes-TCF-v2-0-) – Ryan Baron Aug 26 '20 at 02:39
  • @RyanBaron thank you very much for these examples. I had already come myself to the first version you provided, although it was a more basic version, as, to start with, I just wanted to get rid of the 2.1a error. So I had paused google tags ((adsbygoogle=window.adsbygoogle||[]).pauseAdRequests=1;), but I was still loading their js. Then after receiving tcloaded or useractioncomplete events, I unpaused the ads. Now, am I the only one who thinks that Google shouldn't start doing any checks until ads are unpaused? They even state that no cookies are created while tags are paused. – kRs Aug 26 '20 at 21:31
  • @kRs I completely agree. Google states that all we have to do is pause the ads to be compliant, but then we get the 2.1a error. My ads load slower if I have to wait before loading the entire JS. – John Smith Aug 28 '20 at 04:27
  • you do not need to wait for the confirmation, it seems there is a function for that https://oko.uk/blog/integrating-quantcast-choice-with-google-ad-manager – Radek Sep 04 '20 at 15:42
  • 1
    @radek The oko link you posted is for the TCF v1 (TCF v2 api calls use __tcfapi not __cmp). The consent.googlepersonalizationdata option was added to Quantcast Choice TCF v1 to help publishers get & check for proper consents before Google joined the IAB. With Google joining the IAB, they now have a vendor id 755 so for TCF v2 we need to check the required purpose consents & vendor consent (755) for Google. Here is some additional information on Google consent that may be helpful https://help.quantcast.com/hc/en-us/articles/360052427553-Prepare-for-Google-s-Additional-Requirements-on-TCF-v2-0 – Ryan Baron Sep 08 '20 at 17:28
2

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>
Andres SK
  • 10,779
  • 25
  • 90
  • 152