Attributing Campaigns to Sales Opportunities in Marketo

I’m writing this because it’s the year 2020 and we’re still having trouble attributing onsite campaigns/testing to Marketo MQLs/Opportunities/Revenue.

It is meant primarily for technical practitioners as a quick-start guide, and is so simple you’ll probably be able to get it hooked up today.

As a result, this guide is very tactical in nature, with the end goal of helping you answer the question ‘What impact is my onsite campaigns/tests having on revenue?”.

What solution is for you

If you happen to already be using FunnelEnvy, then enabling our Marketo Integration is all you need to do. This not only enables attribution, but unlocks additional functionality, including the ability to target onsite users based on their Marketo classifications. It’s a paradigm shift in how you think about marketing automation and I encourage you to read our recent article to find out more.

If on the other hand you’re still using a web-based testing tool like Optimizely or Adobe Target to run your onsite campaigns then read on, as this guide is primarily for you. But first, a cautionary tale:

Always optimize for downfunnel outcomes, not onsite vanity conversions

Recently, FunnelEnvy ran a very visible experiment via Adobe Target for a well known SaaS company. Early results were trending downward and web analytic data showed a sharp downtrend in incremental conversions/leads. Talks were had about ending the test early as this was potential a huge economic impact.

Luckily, the attribution module you are about to see had already been installed, allowing us to query Marketo directly, telling a different story. We were able to calculate that the test was actually responsible for a significant increase in annual recurring revenue! Without this additional layer of information, we likely would have moved on, but.

But back to our discussion:

The Solution

We’re going to break this guide up into two main parts:

Part 1: Create/maintain a running list of campaigns/tests a user has seen

Part 2: Pipe this list into Marketo

Create/maintain a running list of campaigns a user has seen.

In order to automate this as much as possible, we’re going to create a centralized module that fires on every page. This allows us to just install it once (via GTM, Launch etc.) and not have to make any edits when new campaigns/tests are launched.

You’ll need to take advantage of Adobe’s Response Tokens or Optimizely’s client side object if you want to go this route. Users of other platforms like Google Optimize will need to follow a more manual approach, adding the module to every new campaign/test. The principles will stay the same, it just requires a bit more to upkeep.

The Code

try{
  (function () {
	// Callback for Adobe Target response tokens
    document.addEventListener(adobe.target.event.REQUEST_SUCCEEDED, function(e) { 
        var tokens = e.detail.responseTokens; 
    
        if (isEmpty(tokens)) { 
            return; 
        } 
    
        var uniqueTokens = distinct(tokens);
 })();
} catch(err) {
  console.log(err);

The start of our module – An Adobe Response Token listener

 //Cycles through each token
uniqueTokens.forEach(function(token) { 

    var cookieName = token["activity.name"] + ' ' + token["experience.name"];

    // Slugify the cookie name.
    	cookieName = cookieName.toLowerCase().replace(/\\((evar.*?)\\)|\\[(.*?)\\]/g, '').trim().replace(/[^a-z0-9]+/g, '-');
});

Next we cycle through each response (there is 1 per active campaign) slugifying the campaign/variation name.

/*
Find the existing cookie if it exists.  Adds new campaign values to the front of the cookie
*/
var existingCookie = _satellite.cookie.get('marketoCookie') || '';

if (existingCookie.indexOf(cookieName) === -1) {
  var newCookie = cookieName + '|' + existingCookie;

  _satellite.cookie.set('marketoCookie', newCookie, {expires: 30});
}

Since this module runs on every page load, we also need handle duplicates in the cookie name

var checkLength = function checkLength() {
  if (newCookie.length > 2000) {
    newCookie = newCookie.split('|');
    newCookie.pop();
    newCookie = newCookie.join('|');

    checkLength();
  }
};
checkLength();

Finally, Marketo inputs (you’ll set this up soon) have a character limit of 2000 characters, so we truncate older campaigns.

Here’s the reusable function in it’s entirety:

try{
  (function () {
	// Callback for Adobe Target response tokens
    document.addEventListener(adobe.target.event.REQUEST_SUCCEEDED, function(e) { 
        var tokens = e.detail.responseTokens; 
    
        if (isEmpty(tokens)) { 
            return; 
        } 
    
        var uniqueTokens = distinct(tokens); 

				//Cycle through each token
        uniqueTokens.forEach(function(token) { 

            var cookieName = token["activity.name"] + ' ' + token["experience.name"];

            // Slugify the cookie name.
            	cookieName = cookieName.toLowerCase().replace(/\\((evar.*?)\\)|\\[(.*?)\\]/g, '').trim().replace(/[^a-z0-9]+/g, '-');

					  /*
					    Find the existing cookie if it exists.  
						  Adds new campaign values to the front of the cookie
						*/
            var existingCookie = _satellite.cookie.get('marketoCookie') || '';

            if (existingCookie.indexOf(cookieName) === -1) {
              var newCookie = cookieName + '|' + existingCookie;
							/* 
								 If above the 2000 Marketo input character limit, 
								 truncate old campaign values
							*/
              var checkLength = function checkLength() {
                if (newCookie.length > 2000) {
                  newCookie = newCookie.split('|');
                  newCookie.pop();
                  newCookie = newCookie.join('|');

                  checkLength();
                }
              };
              checkLength();
              _satellite.cookie.set('marketoCookie', newCookie, {expires: 30});
            }

        });     
    });
    
    function isEmpty(val) { 
        return (val === undefined || val == null || val.length <= 0) ? true : false; 
    } 
 
    function key(obj) { 
        return Object.keys(obj) 
        .map(function(k) { return k + "" + obj[k]; }) 
        .join(""); 
    } 
 
    function distinct(arr) { 
        var result = arr.reduce(function(acc, e) { 
        acc[key(e)] = e; 
        return acc; 
        }, {}); 
    
        return Object.keys(result) 
        .map(function(k) { return result[k]; }); 
    } 
  })();
} catch(err) {
  console.log('Error in Target Marketo Cookie');
}

A simple solution – copy and pasteable.

Create a hidden input field on all Marketo forms

Lastly, we need a way to get the running list into Marketo.

Marketo has out of the box functionality to create an input to ingest a cookies value. All you need to do is add this input to your forms, specify the cookie name (in our case, marketoCookie) and the rest happens by default.

You’ll now have a historical list of campaigns/tests an individual user saw whenever they submit a Marketo form.

Easy peasy.

By | 2020-03-22T19:05:26-07:00 March 22nd, 2020|Uncategorized|0 Comments

About the Author:

Marketing at FunnelEnvy. Love everything growth and strategy