Guide to Tracking Google Ad Manager Impressions and Clicks

When we use Google Ad Manager, the ads are served using an iframe, which makes it somewhat challenging if we want to track the impressions in another tool, such as Google Analytics 4.

This is why, on this occasion, I’ll be sharing a library to programmatically track our ad impressions and clicks.

For this task we’ll relying on the ad tagging library for Google Ad Manager, Google Publisher Tag and the Google Analytics Promotion Tracking from the Ecommerce ( view_promotion , select_promotion events )

You may identify th eGoogle Publish Tag Library for the global variable window.googletag, check the following snippet and the most simpliest example.

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="description" content="Display a fixed-sized test ad." />
  <title>Display a test ad</title>
  <script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
  <script>
    window.googletag = window.googletag || { cmd: [] };

    googletag.cmd.push(() => {
      // Define an ad slot for div with id "banner-ad".
      googletag
        .defineSlot("/123456789/Debug/Em/All", [300, 250], "banner-ad")
        .addService(googletag.pubads());

      // Enable the PubAdsService.
      googletag.enableServices();
    });
  </script>
  <style></style>
</head>

The way the Google Ad Server works is that it creates an iframe with our unit_ad_id as its name or ID, and it handles querying the Google Ad Manager server for details about the ads and the creatives to be served.

If we want to track which ads are being served in our pages, The first challenge we’ll encounter is that we only have information about the ad-unit path, which essentially provides details about the current slot context. Typically, we also need other values, such as the current campaign and the creatives being served. Since all the rendering happens on the iframe we won’t be able to know when the ads has been served, or even have access to the content becuase the content is loading within the iframe.

So, let’s start to figure out how are we going to track our ads, first thing we need to know that despite being working on an iframe ( i know we all hate iframes ), GTP library offers some events that will help us in our mission. ( GPT Reference )

impressionViewable
rewardedSlotClosed
rewardedSlotGranted
rewardedSlotReady
slotOnload
slotRenderEnded
slotRequested
slotResponseReceived
slotVisibilityChanged
Available events on Google Tag Publisher

We want to track the ads impressions, so we’ll be using the impressionViewable event. Which will trigger once the banner has been shown to the user.

This is how we can add a listener for the Google Tag Publisher event.

googletag.pubads().addEventListener("impressionViewable", function(ad) {
	// Do our stuff
});

You may have noticed that the event will return a value ( that we’re naming as ad) that will contain some extra details about the current ad being shown. This will return and slot object, which some functions we can use to grab info about the current ad just rendered.

The primary method we need to examine is ad.slot.getResponseInformation(). This method returns the current advertiserId, campaignId, and creativeId, which we will use to compile our Promotion Impression data.

> ad.slot.getResponseInformation()
{
    "advertiserId": 5632560629,
    "campaignId": 3554088676,
    "creativeId": 138478051191,
    "lineItemId": 6737660555,
    "sourceAgnosticCreativeId": 138478051191,
    "sourceAgnosticLineItemId": 6737660555,
    "isBackfill": false,
    "yieldGroupIds": null,
    "companyIds": null,
    "creativeTemplateId": null,
    "encryptedTroubleshootingInfo": null
}

But not only this, we also want to track to where the ads points to, ie: what’s the clickURL. Sadly none of the method will give us that information, but we can use ad.slot.getHtml()method , which will return the current ads markup and we’ll be using some regex to extract the details.

We will follow this logic

  • Extract all the a href links from the html
  • Filter these href links for those who have a adurl parameter

This is the way we can extract the destination url for our banner

var getAdUrl = function(adHtmlString) {
  // Step 1: Extract all 'a' tags with 'href' attributes
  var aTagRegex = /<a\s+[^>]*href\s*=\s*["']?([^"'>\s]+)["']?[^>]*>/gi;
  
  let matches;
  var hrefs = [];
  while ((matches = aTagRegex.exec(adHtmlString)) !== null) {
    hrefs.push(matches[1]); // Capture the 'href' value
  }
  
  // Step 2: Filter hrefs that contain the 'adurl' parameter
  var adurlHrefs = hrefs.filter(href => href.includes('adurl='));
  
  // Step 3: Extract the 'adurl' parameter value from these hrefs
  var adurlValues = adurlHrefs.map(href => {
    var urlParams = new URLSearchParams(href.split('?')[1]);
    return urlParams.get('adurl');
  });  
}

var adHtmlString = ad.slot.getHtml()
console.log(getAdUrl(adHtmlString));

Let’s quickly recap. Now, we can capture the Ad-unit Path, the campaignId, the creativeId, and the destination URL for the impressions of our ads, which provides us with valuable information. This means we can build a dataLayer.push({}) with a view_promotion event to track our impressions. Alternatively, we can send this information to any other tool you prefer. We’ll be using a Google Tag Manager (GTM) dataLayer.push as it is the most commonly used tag management system (TMS). Feel free to adapt this core implementation for your needs or reach out to me for help in customizing it to your site’s requirements.

Next Challenge is: “How do we track the clicks to the iFrame“. the <iframe> element doesn’t have a click event, which doesn’t help in our task, we can luckily detect the clicks within the iframe.contentWindow since it’s not hosted on a remote domain ( no src )

document.querySelector('#div_ad_holder iframe').contentWindow.document.body.onclick = function(e) {
	alert("Ad Clicked");
}

At this point, we should know how to detect when an impression has been rendered, extract all the details about the ad’s impression, and track the clicks on the ads. We have everything we need; it’s just a matter of putting everything together to create a fully functional tracking solution.

To make the process easier for everyone, I’ve started a new GitHub repository that provides a simple, ready-to-use library. This library will push the data as an Ecommerce Promotion event to the GTM dataLayer.

Find it here: https://github.com/analytics-debugger/google-ad-manager-tracker

(function() {
    // Copyright Analytics Debugger S.L.U. All Rights Reserved. 2024
    // Author: David Vallejo
    // Date: 2024-06-21
    // Name: Google Publisher Tag Impressions and Clicks Tracker
    // This file is licensed under the MIT License.
    // License text available at https://opensource.org/licenses/MIT  
  
    // Add ?ad_debug=1 for debugging messages
    var debug = document.location.search.includes('ad_debug=1') ? true : false;
    // Init googletag variable, jic
    window.googletag = window.googletag || {
        cmd: []
    };
    // Init googletag variable, jic
    window.dataLayer = window.dataLayer || [];
    // Our variable for holding the impressions info. Used for grabbing the data on click
    window.__gpt_impressions = window.__gpt_impressions || [];

    // Helper function to extract the ad destination URL from the Ad HTML
    var getAdUrl = function(adHtmlString) {
        // Step 1: Extract all 'a' tags with 'href' attributes
        var aTagRegex = /<a\s+[^>]*href\s*=\s*["']?([^"'>\s]+)["']?[^>]*>/gi;

        let matches;
        var hrefs = [];

        while ((matches = aTagRegex.exec(adHtmlString)) !== null) {
            hrefs.push(matches[1]); // Capture the 'href' value
        }

        // Step 2: Filter hrefs that contain the 'adurl' parameter
        var adurlHrefs = hrefs.filter(href => href.includes('adurl='));

        // Step 3: Extract the 'adurl' parameter value from these hrefs
        var adurlValues = adurlHrefs.map(href => {
            var urlParams = new URLSearchParams(href.split('?')[1]);
            return urlParams.get('adurl');
        });
        if (adurlValues.length > 0) return adurlValues[0];
    }
    // Adding the impression Listener
    googletag.cmd.push(function() {
        googletag.pubads().addEventListener("impressionViewable", (event) => {
            // We have an impression, let's get the holder iframe reference and add the click event.
            document.querySelector('#' + event.slot.getSlotElementId() + ' iframe').contentWindow.document.body.onclick = function(e) {
                var impressionInfo = window.__gpt_impressions.filter(function(e) {
                    if (e.promotion_name === event.slot.getSlotElementId()) return true;
                });
                window.dataLayer.push({
                    event: 'select_promotion',
                    ecommerce: {
                        items: [impressionInfo]
                    }
                })
                if (debug === true) console.log("GPT AD CLICK", impressionInfo);
            }

            var slotDetails = event.slot.getResponseInformation();
            try {
                var impressionInfo = {
                    promotion_name: event.slot.getSlotId().getDomId(),
                    promotion_id: slotDetails.campaignId.toString(),
                    creative_name: slotDetails.creativeId.toString(),
                    creative_slot: getAdUrl(event.slot.getHtml())
                }
                window.dataLayer.push({
                    event: 'view_promotion',
                    ecommerce: {
                        items: [impressionInfo]
                    }
                });
                window.__gpt_impressions.push(impressionInfo);
                console.log("GPT AD IMPRESSION", impressionInfo);
            } catch (e) {
		            if (debug === true) console.log("GPT ERROR GRABBING IMPRESSION DETAILS: ", e);
	          }
        });
    });
})()

The earlier code snippet is responsible for pushing the necessary data to Google Tag Manager. At this stage, you just need to add the appropriate tags and triggers in Google Tag Manager to ensure the data flows into your account.

This code has only been tested on Chromium-based browsers. While the impressions should work across all browsers, we are unsure of how Safari and Firefox will behave with regard to the clicks.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.