diff --git a/lib/static_resources.js b/lib/static_resources.js
index 8f88114a98a55ebb9388d63900121d624762dca8..f877ca51a84f7d4b4c7e3b9ae0a94ddf9a07373a 100644
--- a/lib/static_resources.js
+++ b/lib/static_resources.js
@@ -80,6 +80,7 @@ var dialog_js = und.flatten([
     '/shared/command.js',
     '/shared/history.js',
     '/shared/state_machine.js',
+    '/shared/interaction_data.js',
 
     '/dialog/resources/internal_api.js',
     '/dialog/resources/helpers.js',
diff --git a/lib/wsapi/interaction_data.js b/lib/wsapi/interaction_data.js
index a634625316e1a3964a2765f42da7eb50be47460e..03a77a5a999253a04670da84ce2673b3806d7e12 100644
--- a/lib/wsapi/interaction_data.js
+++ b/lib/wsapi/interaction_data.js
@@ -57,9 +57,8 @@ exports.process = function(req, res) {
 
   if (req.body.data) {
     var kpi_json = req.body.data;
-    logger.debug("Simulate write to KPI Backend DB - " + kpi_json);
+    logger.debug("Simulate write to KPI Backend DB - " + JSON.stringify(kpi_json));
     try {
-      JSON.parse(kpi_json);
       store(kpi_json, function (store_success) {
         sendResponse(store_success);
       });
diff --git a/resources/static/shared/network.js b/resources/static/shared/network.js
index 2f7049e6ac98739b1f0ce8c2311fa0226cca8d40..60651aff69af869b2a98583e7e39253cb9404906 100644
--- a/resources/static/shared/network.js
+++ b/resources/static/shared/network.js
@@ -352,6 +352,28 @@ BrowserID.Network = (function() {
       });
     },
 
+    /**
+     * post interaction data
+     * @method setPassword
+     * @param {string} password - new password.
+     * @param {function} [onComplete] - Callback to call when complete.
+     * @param {function} [onFailure] - Called on XHR failure.
+     */
+    sendInteractionData: function(data, onComplete, onFailure) {
+      post({
+        url: "/wsapi/interaction_data",
+        data: {
+          // reminder, CSRF token will be inserted here by xhr.js, that's
+          // why this *must* be an object
+          data: data
+        },
+        success: function(status) {
+          complete(onComplete, status.success);
+        },
+        error: onFailure
+      });
+    },
+
     /**
      * Update the password of the current user
      * @method changePassword
diff --git a/resources/static/shared/storage.js b/resources/static/shared/storage.js
index c1a6ec6f5426d2e3d4c01687571b6d92c04f09f5..16cd8cd13cb75acb2267d993da3cc477099fe727 100644
--- a/resources/static/shared/storage.js
+++ b/resources/static/shared/storage.js
@@ -379,6 +379,58 @@ BrowserID.Storage = (function() {
     storage.emailToUserID = JSON.stringify(allInfo);
   }
 
+  function pushInteractionData(data) {
+    var id;
+    try {
+      id = JSON.parse(storage.interactionData);
+      id.unshift(data);
+    } catch(e) {
+      id = [ data ];
+    }
+    storage.interactionData = JSON.stringify(id);
+  }
+
+  function currentInteractionData() {
+    try {
+      return JSON.parse(storage.interactionData)[0];
+    } catch(e) {
+      alert(e);
+      return {};
+    }
+  }
+
+  function setCurrentInteractionData(data) {
+    var id;
+    try {
+      id = JSON.parse(storage.interactionData);
+      id[0] = data;
+    } catch(e) {
+      alert(e);
+      id = [ data ];
+    }
+    storage.interactionData = JSON.stringify(id);
+  }
+
+  function getAllInteractionData() {
+    try {
+      var id = JSON.parse(storage.interactionData);
+      return id.slice(1);
+    } catch(e) {
+      alert(e);
+      return [];
+    }
+  }
+
+  function clearInteractionData() {
+    try {
+      var id = JSON.parse(storage.interactionData);
+      storage.interactionData = JSON.stringify(id.slice(0,1));
+    } catch(e) {
+      alert(e);
+      delete storage.interactionData;
+    }
+  }
+
   return {
     /**
      * Add an email address and optional key pair.
@@ -451,6 +503,43 @@ BrowserID.Storage = (function() {
       remove: generic2KeyRemove.curry("main_site", "signInEmail")
     },
 
+    interactionData: {
+      /**
+       * add a data interaction blob to localstorage
+       * @param {object} data - an object to push onto the queue
+       * @method interactionData.push()
+       * @returns nada
+       */
+      push: pushInteractionData,
+      /**
+       * read the current interaction data blob (the one on the top of the
+       * stack)
+       * @method interactionData.current()
+       * @returns a JSON object containing the latest interaction data blob
+       */
+      current: currentInteractionData,
+      /**
+       * overwrite the current interaction data blob (the one on the top of the
+       * stack)
+       * @param {object} data - the object to overwrite current with
+       * @method interactionData.setCurrent()
+       */
+      setCurrent: setCurrentInteractionData,
+      /**
+       * get all the saved interaction data (returned as a JSON array)
+       * @method interactionData.get()
+       * @returns an array, possibly of length zero if no interaction data is
+       * available
+       */
+      get: getAllInteractionData,
+      /**
+       * clear all interaction data, except the current, in-progress
+       * collection.
+       * @method interactionData.clear()
+       */
+      clear: clearInteractionData
+    },
+
     usersComputer: {
       /**
        * Query whether the user has confirmed that this is their computer
diff --git a/resources/static/shared/xhr.js b/resources/static/shared/xhr.js
index daf6f607bb23ab40d3e082529de0bbd6b19db48a..2d86c6fde9fa421161c56f142bad9562482830d9 100644
--- a/resources/static/shared/xhr.js
+++ b/resources/static/shared/xhr.js
@@ -132,7 +132,6 @@ BrowserID.XHR = (function() {
     withContext(function() {
       var data = options.data || {};
       data.csrf = data.csrf || csrf_token;
-
       var req = _.extend(options, {
         type: "POST",
         data: JSON.stringify(data),