The Marketing Technologist.

We talk about analytics, code, data science and everything related to marketing technology. Backed by the tech geeks of Greenhouse Group.

Working with Creative Macros in AppNexus hosted creatives

As a creative developer, one of the best features of AppNexus is their huge number of creative macros. We use those macros for quite some purposes, like reading query string parameters, reading user context information like state and device type and to do some advanced click tag magic. Their creative macros look something like this: ${MACRO_NAME}, and as a developer, you can use it as a placeholder in your code and have it fulfilled when your ad runs live. Let's say you want to show the user's state in your HTML5 creative, you would use something like this:

var userState = '${USER_STATE}';
var message = 'You live in the state: ' + userState;
document.querySelector('#text').innerHTML = message;

Until recently, this snippet would mostly work like a charm. But a few months ago, AppNexus added a new feature that allows us to host the HTML5 creatives at their server, instead of putting a third party tag in their system. For an ad server, this is a great feature basic requirement. This is a good improvement and we're very happy with it, but they forgot to build in support for creative macros for these hosted creatives, and it doesn't look like they will add support soon. What? For a moment, it caught us off guard.

The solution: third party raw JS tag & postMessage

Because we heavily rely on creative macros, both in our roles as creative developers at the Creative Hub at Greenhouse Group as software engineers at LemonPI (check it out!), we were very eager to find a solution.

Luckily, creative macros still work when you use them in AppNexus' third-party impression pixels (both raw JS, image/image/JavaScript URL). But as the creative does not live in the same frame as the third party pixels, we need some way to communicate with the creative. And as it did tons of times already, the postMessage API saved our ass here. If you are not familiar with postMessage: it allows cross-iframe communication through events.

The best approach is to start sending an event from the creative to the parent iframe - where the third party tag lives - telling it that the creative is ready to receive macros:

parent.postMessage({type: 'CREATIVE_READY'}, '*');

In the AppNexus console, you should add a Raw JS third part tag. All this code does is wait for the CREATIVE_READY event, and then send the ${USER_STATE} macro to the creative.

window.addEventListener('message', function(event) {
  if (event.data && event.type === 'CREATIVE_READY') {
    event.source.postMessage({
      type: 'CREATIVE_MACRO',
      userState: '${USER_STATE}'
    }, event.origin);
  }
}

Because the creative already sends its own Window reference to the parent through the source property of the event argument, we don't need to figure out to what iframe we should send the macro. Make sure you both click Add pixel and Save in the bottom of the screen in order to properly save your new pixel.

In the creative, all you do is wait for the macros to be sent from the parent frame:

var startCreative = function(userState) {
  var message = 'You live in the state: ' + userState;
  document.querySelector('#text').innerHTML = message;
}

// Wait for the parent frame to send the creative macro
window.addEventListener('message', function(event) {
  if (event.data && event.type === 'CREATIVE_MACRO') {
    var userState = event.data.userState;
    startCreative(userState);
  }
}

Note that AppNexus does not fire third party pixels in its Console environment, so you need to go to https://ib.adnxs.com/cr?id=YOUR_CREATIVE_ID to test your implementation. It is updated +_ 15 minutes after you saved your creative.

We've thoroughly tested this technique in multiple live creatives, and it works amazingly fine. Of course, it's a bit of a hack, but it works, and we'll keep working with creative macro's until AppNexus finally decided to support it in their hosted creatives by default.