mpknowledgedrop

All About Ad Blockers

  • 26 October 2020
  • 0 replies
  • 87 views
All About Ad Blockers
Userlevel 1
  • Mixpanel Community Team
  • 1 reply

Let's talk about ad-blockers! 

Love them or hate them, there is no denying that ad blockers are part of the Internet’s ecosystem. And because they run client-side, with a very broad set of permissions and control over the browser, they definitely have an effect on the end-user experience when using a web application. So, we should try to measure ad blockers, the users who employ them, and understand how they affect our key metrics… right?

Easier said than done! 

It can be tricky to track users who have ad blockers enabled, because ad blockers often block data trackers and third party SDKs. In particular, many ad blockers regard Mixpanel’s SDK (and the mixpanel.com domain) as a “third-party tracker” and stop any .track() calls dead in their tracks (pun somewhat intended).

We can grumble about this all day long (“we’re NOT serving ads!”, “we’re using first-party cookies!”, “we’re collecting product usage data and we already have the user’s consent”) - but the fact of the matter is that we have no control over the logic that ad blocker developers employ in their plugins, and so we have to take it as a given that ad blockers, out there in the wild, might be affecting the quality and quantity of data we are able to collect about how our end-users use our applications.

But that doesn’t mean we’re out of luck. After all, ad blockers are just browser extensions, written by humans, and therefore we can address them with more code, written by humans.

This post will discuss three ways to address ad blockers in your web application, in order of complexity, but before we dive into that, let’s first understand (at a high level) how ad blockers work.

 

HOW AD BLOCKERS WORK

There are literally hundreds of different ad blockers that are freely available, but they all generally do the same sorts of things:

  • Crawl through the window (global) object in the browser, hunting for a namespace that matches a set of common tools that are designed to power advertising and tracking tools(in our case, the mixpanel object).

  • If the ad blocker finds any such libraries (Segment, Mixpanel, Google Analytics, etc...), it clears out the object and freezes it, so it can't do anything.

if (mixpanel) {

mixpanel = {}; //destroy the mixpanel object

Object.freeze(mixpanel); //keep it from initializing itself again

}
  • Some ad blockers will also maintain a list of domains which they've identified as 'trackers' or 'ad services' and block all GET and POST requests to those domains (in our case api.mixpanel.com).

  • Some ad blockers will go even further to block any <script> tags where the src attribute matches one their ‘list of domains’ …  the extension  will issue a 307 (internal redirect) which prevents the browser from loading the Mixpanel SDK properly

  • And finally, some ad blockers will tell the end-user they have blocked a ‘tracker’ or ‘ad services,’ which allows the user to whitelist this particular service.

(in many cases, however, the ad blocker will do this in the background, silently)

It’s important to remember that nearly all of the ad blocker’s code is running client-side in the user's browser (generally via a content script inside a browser extension). Content scripts wield a tremendous amount of power and privilege in the browser, and so we are fairly limited on the ways we can intervene.

So what can we do? I’ll suggest 3 solutions, ordered from least to most complex:

 

HOW TO DEAL WITH AD BLOCKERS

1. Detect if the user has an adblocker, and if so, ask them to disable it

This might sound a bit cheeky, but with new data regulations (like GDPR, CCPA, etc...) we have to accept the reality that some user's do not wish to be tracked.

The good news is that it's relatively straightforward to 'check' if a user is using an adblocker; here's some example code in javascript which does exactly that (by baiting the adblocker with a juicy JS file call ads.js)

<!--- ads.js --->
<script>
var e=document.createElement('div');
e.id='RZfrHsidDwbG';
e.style.display='none';
document.body.appendChild(e);
</script>


<!-- index.html -->
<script src="/ads.js" type="text/javascript"></script>
<script type="text/javascript">

if (document.getElementById('RZfrHsidDwbG')) {

console.log('user is probably not using an ad blocker');

} else {

console.log('user definitely is using an ad blocker');

}

</script>

(more info: https://www.detectadblock.com/)

Based on the outcome of this script call, you could drop a pop-up on the page that asks the user to disable their ad blocker on your page: 

Certainly, not everyone will comply with your wishes here, but this signifies a compromise between your desire to understand what your users are doing and the users' rights to privacy.  

This nice part about this solution is that it can be entirely done with client-side code in the browser, without needing to make any changes on your server.

If you’d prefer to use a package that offers more robust “ad blocker detection” (rather than vanilla javascript), there are many such packages available.

 

2. Track events server-side

The bulletproof way to avoid ad blockers is to send data from your web server directly to Mixpanel. The ad blocker can't touch the code on your webserver, and therefore cannot ‘prevent’ the data from being sent to Mixpanel.

This is not as scary as it sounds; Mixpanel maintains full-featured server-side SDKs to make tracking-on-the-server. For example, the browser code:

// track an event in javascript

mixpanel.track('button clicked', {'label': 'sign up'})

would become:

// track an event in PHP

$mp->track("button clicked", array("label" => "sign-up"));

The only thing we miss with server-side tracking “out-of-the-box” are Mixpanel’s default properties … however, this can still be parsed and sent from your server, it just takes a bit of extra work.

 

3. Proxy the client-side requests through our own server, don’t rely on the Mixpanel SDK

Ad blockers are not omniscient… they can be fooled just like any other piece of software. If we keep in mind the general rules and patterns they follow (outlined early), we can come up with a solution that bypasses ad blockers by:

  • Not relying on an SDK to track events

  • Not routing requests directly to api.mixpanel.com

Essentially what we are going to do is this: 

  • If the user is using an ad blocker (see #1) 

    • Make the mixpanel requests with the browser's XMLHttpRequest object (since we can't count on the library being available) 

    • Route the data to a different server (via proxy) which then forward the requests to Mixpanel.

In the demo below, I'm using Rob W's CORS Anywhere as my proxy, which he maintains as a free service (though this should not be used on production)... however he does publish the source code if you want to stand this up and host it on your own. 

Here's some example code which implements track() as trackStuff():

function trackStuff(user, eventName, props, mixpanelProjectIdentifier) {    
var uuid = user || "123-456-789"    
var token = mixpanelProjectIdentifier    
var properties = props;        

//the mixpanel data model    
var spec = {        
"event": eventName,
        "properties": {
// these two properties are required!             
"distinct_id": uuid,
            "token": token,
            // but any number of properties can be passed in
           "$current_url": document.location.href,
            "foo": ['bar', 'baz', 'qux']        
}    
}  

// iterate through 'passed in' props    
if (properties) {        
for (prop in properties) {            
spec.properties[prop] = properties[prop];        
}    
}    
// base64 encode    
var payload = btoa(JSON.stringify(spec))    
//make a vanilla HTTP request as per: https://developer.mixpanel.com/docs/http#section-tracking-events    
var xhttp = new XMLHttpRequest();    
xhttp.onreadystatechange = function() {        
if (this.readyState == 4 && this.status == 200) {            
console.log('data sent')        
}    
};    

// use Rob W's cors-anywhere to PROXY the https requests    
xhttp.open("GET", "https://cors-anywhere.herokuapp.com/https://api.mixpanel.com/track/?data=" + payload + '&ip=1', true);    
xhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');    
xhttp.send();
}


// how we might use this
var someProps = {
"user type": "admin",
"interests": ['foo', 'bar', 'baz', 'qux']
};
trackStuff('ak', 'Page Viewed', someProps, '009c696fd3f040f5b32297ac50845210');

Here's a demo of it working on a page where ghostery is blocking Mixpanel, but we get the event to send anyway:

So we’ve outlined three different approaches to dealing with ad blockers. The best practice here is to choose the path of least resistance, so we don’t sacrifice data quality but don’t spin our wheels spending too much time on edge-cases.

Ultimately, many customers may employ a hybrid model of the approaches listed above… for example:

  • Track “ really important” events of the server

  • Ask the user to disable ad block (if present)

  • Track the user anonymously if they refuse to disable ad block

What are some of the methods you’ve tried to deal with ad blockers?  I’d love to hear from the community!


0 replies

Be the first to reply!

Reply