Adding a ‘hitCallback’ feature to Munchkin

Marketo Munchkin and Google Analytics have a lot in common on the client side, but GA’s hitCallBack feature is something Munchkin ain’t got.

hitCallback lets you trigger a JS action as soon as possible after GA finishes logging a hit: not one millisecond before, and not some gross-grained period after, but exactly at the right time.

When you’re redirecting away from a page via location.href, hitCallback ensures the pageview shows up in GA. Rather than guessing “GA usually takes less than 2 seconds” — or worse, not even thinking about GA’s network needs at all — you do this:

ga("send", "pageview", {
  hitCallback: function() {
    location.href = "/followup-page.html";
  }
});

I’ve been planning to rewrite my Marketo Redirector LP code to use similar logic. Currently, the Redirector JS uses a (configurable, but still guessy) timeout + progress visualization before redirecting. Much better, needless to say, would be redirecting as soon as you can guarantee Munchkin has acknowledged the Visit Web Page.

To do that, we need an event or callback that fires at that exact moment. No guesswork.

The goal

Instrumenting an existing library that only exists as minimized JS is a pretty foolish thing in general.

But in this case we have a huge advantage: we only need to know when the Munchkin library uses two built-in JS features: Beacons (in the vast majority of modern browsers) and standard XMLHttpRequest (in browsers that are pre-Beacon).

The fact that Munchkin now supports Beacons (it didn’t until recently) makes our additional code unintrusive ~95% of the time. And we don’t actually need to intercept the call to sendBeacon (which would be intrusive).

Instead, we merely need to know that Munchkin will use Beacons in the current browser, and wait for the Munchkin library (the real library, not the initial bootstrapper) to load. When it’s loaded, we know it’s also issued the Beacon call and we’re good to go.

The other ~5% of the time, we do need to eavesdrop on calls to XMLHttpRequest.open and XMLHttpRequest.send, but we release our eavesdropper/interceptor code as early as possible, so it’s not intruding on other Ajax tasks (not that it would break anything anyway!).

The code

You can grab the code from here:

teknkl-munchkinhitevent-v1.js

Download that file and reupload it to Marketo Design Studio (or to your company’s webserver if you prefer).

The usage

Using the new event is simple, but the order-of-operations is crucial.

Your code/markup needs must be in this order:

➊ the remote script teknkl-munchkinhitevent-v1.js

➋ run the created function window.enableMunchkinHitEvent(), optionally passing forceAfter and debug arguments

➌ add an event listener on the window for the custom event named munchkin.visitWebPage

➍ the usual Munchkin embed code

Here’s an example:

<!-- TEKNKL custom libraries -->
<script id="teknkl-munchkinhitcallback-v1" src="https://pages.example.com/rs/123-ABC-456/images/teknkl-munchkinhitevent-v1.js"></script>
<!-- /TEKNKL custom libraries -->

<!-- enable event and add listener -->
<script>
   enableMunchkinHitEvent({ forceAfter: 4000, debug: true });
   
   document.addEventListener("munchkin.visitWebPage", function(e){
      
     // detail includes properties: transport, usedFallback, timeElapsed
     alert("Munchkin hit complete!" + "\n" + JSON.stringify(e.detail,null,2));
      
     // do your redirect or any other action here 
     // document.location.href = "https://www.example.com/the/final/destination.html";
   });
   
</script>
<!-- /enable event and add listener -->

<!-- standard Munchkin embed -->
<script type="text/javascript">
   (function() {
      var didInit = false;

      function initMunchkin() {
         if (didInit === false) {
            didInit = true;
            Munchkin.init('410-XOR-673', {
               domainLevel: 2
            });
         }
      }
      var s = document.createElement('script');
      s.type = 'text/javascript';
      s.async = true;
      s.src = '//munchkin.marketo.net/munchkin-beta.js';
      s.onreadystatechange = function() {
         console.log("in munchkin", this.readyState)
         if (this.readyState == 'complete' || this.readyState == 'loaded') {
            initMunchkin();
         }
      };
      s.onload = initMunchkin;
      document.getElementsByTagName('head')[0].appendChild(s);
   })();
</script>
<!-- /standard Munchkin embed -->

enableMunchkinHitEvent() takes 2 arguments, both related to timeout behavior:

  • forceAfter {integer, default 4000}: number of milliseconds after which the event will be forcibly fired, even if Munchkin has not responded. (Remember, Munchkin might be disabled by a tracking protection feature or plugin, but we still need to get the person to their final destination!) If the timeout was exceeded, then the event.details.usedFallback will be true. Note this feature can also be used to determine if Munchkin is supported (by definition!).
  • debug {boolean, default true}: also log a line to the F12 Console if the timeout expired.

Coupla notes

Deploy this code sparingly as it’s only necessary in specific circumstances — namely, when you want to redirect both accurately and as soon as possible after the page loads.

I wouldn’t bother using it when substantive human actions must occur in the interim. For example, teeeeeechnically we can’t even guarantee that Munchkin has logged a hit by the time a person successfully submits (meaning manually submits)  a Marketo form. But we take it as a given that Munchkin must’ve fired at some point, or else we’d have to pause the onSuccess callback until we got confirmation. Don’t worry about that case.