Bug-like behavior in Forms 2.0 w/multiple Munchkin cookies

This one's more of a technical curiosity than a pressing concern for most people. But if it's biting you, it's definitely noteworthy.

As you learned (right?) from the post on 2-letter TLDs, Munchkin doesn't intelligently handle registered domains like example.io.

It's easy to set the right domainLevel and fix up your tracking, either manually or via the domain auto-finder in the earlier post. But if you didn't do this from the moment your instance went live, and less than 2 years have gone by since you put in the fix — pretty likely, since I only publicized the issue in late 2016 — then some of your leads will have multiple cookies: older ones from www.example.io and pages.example.io, and the newer one from the correct parent example.io.

From a pure Munchkin standpoint, you're fine. Once you fix the domainLevel, the library will peek into the values of accessible _mkto_trk cookies* to see if they contain the right parent domain. That is, it will use its standard cookie pattern as a regex to see if there's a cookie matching that domain + the Munchkin ID; if one doesn't exist, it'll be created. That'll be the cookie sent with standard Visits Web Page and Clicks Link events.

But what about a Marketo form, which doesn't even require that the Munchkin library be loaded on the page? (Related question: “I disabled Munchkin, so why is there still a cookie?” and Community threads like this.)

A form looks for a _mkto_trk cookie to send as a hidden field in order to associate the lead: any cookie with that name, even one from the wrong, old domain. But how could it even know if the cookie is “wrong” or “right”? After all, the Munchkin config is not present on the page, so there's no domainLevel to check, and there's no corresponding functionality for forms alone.

Cookie names aren't unique

Let's get a little deeper into how cookies work (NSFPeople who are easily bored).

You can have multiple cookies with the same name, as long as each is for a different subdomain at or under your registered domain. So if I were on a.deep.subdomain.example.com, I could have up to 4 cookies with the same name, myCookie:

  • one set at the full domain .a.deep.subdomain.example.com
  • one set at the immediate parent .deep.subdomain.example.com
  • one set at the in-between .subdomain.example.com
  • one set at the uppermost private domain .example.com

And browsers are very cagey about where cookies are sent from and to. Your document.cookie string, the source of all browser knowledge about cookies** will just look like this:

myCookie=one; myCookie=two; myCookie=three; myCookie=four

Doesn't matter how cool your JavaScript cookie parser code is, it just has that string to draw on. The browser will automatically send all of those cookies with your request to *.example.com, but which one is the right one to send over to Marketo for tracking cannot be known based on the cookie name alone.

Now, if you look for specific cookie values, then you can start to distinguish which ones came from where. (Well, which ones probably came from where, since anyone's code can write whatever cookie it wants, as long as it's running on your domain. Assume for our purposes that all cookies were legitimately set by your own code.)

Check out this more self-identifying document.cookie string:

_mkto_trk=123-from-teknkl.com; _mkto_trk=456-from-blog.teknkl.com

In this case, you can see (again, trusting the creator of these cookies) that the first _mkto_trk was set at the uppermost domain, while the second _mkto_trk was set by a deeper subdomain. If the Munchkin domainLevel was set to to 2 (which you'd typically want here) then the one from teknkl.com (value 123-from-teknkl.com) is chosen by Munchkin.

But forms don't peek into the cookie values, just at cookie names.

So what's the bug?

It's like this: if you have multiple cookies, the form will, in effect, randomly choose a _mkto_trk cookie to send to Marketo. This may or may not be the cookie that's actively used by Munchkin per the domainLevel.

So you'll see the Filled Out Form activity itself tied to the appropriate lead (with their Email Address field matching the Email field on the form). But the active Munchkin session will stay anonymous if an old Munchkin cookie was the one added to the form.

And the fix?

Here's a little JS function that can climb the domain “tree” (from the root to the current hostname, dot by dot) and run an action at each domain level. You also pass it a skipDomains array and those domains will be ignored.

function climbDomains( skipDomains, cb, scanDomain ) {
  var domainPath = [],
      skipDomains = skipDomains || [],
      scanDomain = scanDomain || document.location.hostname;
  
  scanDomain
    .split('.')
    .reverse()
    .reduce(function(p,n){
      var domain = [n,p].join('.');
      domainPath.push(domain);
      return domain;
    });

  domainPath
    .forEach(function(domain){
      if ( skipDomains.indexOf(domain) == -1 ) {
        cb(domain);
      }
    });
}

You'd call it like this, to delete the _mkto_trk cookie from all domains except example.com:

climbDomains( ['example.com'], function (domain){ 
  var cookieName = '_mkto_trk',
      EPOCH_ZERO = new Date(0).toGMTString();

  // delete Munchkin cookie at each level
  document.cookie = cookieName + '=;' +
                    'path=/;' +
                    'domain=.' + domain + ';' +
                    'expires=' + EPOCH_ZERO;
});

This will clear out the old cookies, so they'll never be mistakenly used. Good to run this script once per session if you changed your domainLevel at some point.


Notes

* With an exception that I'll explore at another time.

** OK, you can archive the document.cookie, try to delete the cookie name at each possible domain, and see at which levels the cookie is deleted. Kind of the reverse of the domain finder script. But wow, a pain. Also, newer versions of Chrome let JS inspect the cookie store directly, but this is far from cross-platform yet.