diff --git a/browserid/static/dialog/resources/network.js b/browserid/static/dialog/resources/network.js
index f0fd530ddf3b84bc9e43ff9c545f5dec316dbebe..234624f1365e124b01f3aab02ec00cac88b1f1c6 100644
--- a/browserid/static/dialog/resources/network.js
+++ b/browserid/static/dialog/resources/network.js
@@ -40,35 +40,10 @@ BrowserID.Network = (function() {
   var csrf_token,
       xhr = $,
       server_time,
-      auth_status;
+      auth_status,
+      hub = OpenAjax.hub;
 
-  function withContext(cb, error) {
-    if (typeof auth_status === 'boolean' && typeof csrf_token !== 'undefined') cb();
-    else {
-      xhr.ajax({
-        url: "/wsapi/session_context",
-        type: "GET",
-        success: function(result) {
-          csrf_token = result.csrf_token;
-          server_time = {
-            remote: result.server_time,
-            local: (new Date()).getTime()
-          };
-          auth_status = result.authenticated;
-          _.defer(cb);
-        },
-        error: error,
-        dataType: "json"
-      });
-    }
-  }
-
-  function clearContext() {
-    var undef;
-    csrf_token = server_time = auth_status = undef;
-  }
-
-  function createDeferred(cb) {
+  function deferResponse(cb) {
     if (cb) {
       return function() {
         var args = _.toArray(arguments);
@@ -79,6 +54,19 @@ BrowserID.Network = (function() {
     }
   }
 
+  function get(options) {
+    xhr.ajax({
+      type: "GET",
+      url: options.url,
+      // We defer the responses because otherwise jQuery eats any exceptions 
+      // that are thrown in the response handlers and it becomes very difficult 
+      // to debug.
+      success: deferResponse(options.success),
+      error: deferResponse(options.error),
+      dataType: "json"
+    });
+  }
+
   function post(options) {
     withContext(function() {
       var data = options.data || {};
@@ -91,12 +79,53 @@ BrowserID.Network = (function() {
         type: "POST",
         url: options.url,
         data: data,
-        success: options.success,
-        error: options.error
+        // We defer the responses because otherwise jQuery eats any exceptions 
+        // that are thrown in the response handlers and it becomes very difficult 
+        // to debug.
+        success: deferResponse(options.success),
+        error: deferResponse(options.error)
       });
     }, options.error);
   }
 
+  function withContext(cb, onFailure) {
+    if (typeof auth_status === 'boolean' && typeof csrf_token !== 'undefined') cb();
+    else {
+      xhr.ajax({
+        url: "/wsapi/session_context",
+        success: function(result) {
+          csrf_token = result.csrf_token;
+          server_time = {
+            remote: result.server_time,
+            local: (new Date()).getTime()
+          };
+          auth_status = result.authenticated;
+          cb();
+        },
+        error: onFailure
+      });
+    }
+  }
+
+  function clearContext() {
+    var undef;
+    csrf_token = server_time = auth_status = undef;
+  }
+
+  // Not really part of the Network API, but related to networking
+  $(document).bind("offline", function() {
+    hub.publish("offline");
+  });
+
+  function xhrError(callback, error) {
+    return function() {
+      if (callback) {
+        callback();
+      }
+      hub.publish("xhrError", error);
+    };
+  }
+
   var Network = {
     /**
      * Set the XHR object and clear all context info.  Used for testing.
@@ -134,7 +163,7 @@ BrowserID.Network = (function() {
               // session, let's set it to perhaps save a network request
               // (to fetch session context).
               auth_status = authenticated;
-              _.delay(onSuccess, 0, authenticated);
+              if(onSuccess) onSuccess(authenticated);
             } catch (e) {
               onFailure("unexpected server response: " + e);
             }
@@ -155,7 +184,7 @@ BrowserID.Network = (function() {
       withContext(function() {
         try {
           if (typeof auth_status !== 'boolean') throw "can't get authentication status!";
-          _.delay(onSuccess, 0, auth_status);
+          if (onSuccess) onSuccess(auth_status);
         } catch(e) {
           if (onFailure) onFailure(e.toString());
         }
@@ -178,7 +207,7 @@ BrowserID.Network = (function() {
           // FIXME: we should return a confirmation that the
           // user was successfully logged out.
           auth_status = false;
-          if (onSuccess) _.defer(onSuccess);
+          if (onSuccess) onSuccess();
         },
         error: onFailure
       });
@@ -200,10 +229,7 @@ BrowserID.Network = (function() {
           site : origin
         },
         success: function(status) {
-          var staged = status.success;
-          // why a delay here? Because of the test harness?
-          // shouldn't the delay be in the test harness?
-          _.delay(onSuccess, 0, staged);
+          if (onSuccess) onSuccess(status.success);
         },
         error: onFailure
       });
@@ -218,10 +244,10 @@ BrowserID.Network = (function() {
      * I think so (BA).
      */
     emailForVerificationToken: function(token, onSuccess, onFailure) {
-      xhr.ajax({
+      get({
         url : "/wsapi/email_for_token?token=" + encodeURIComponent(token),
         success: function(data) {
-          onSuccess(data.email);
+          if (onSuccess) onSuccess(data.email);
         },
         error: onFailure
       });
@@ -234,12 +260,10 @@ BrowserID.Network = (function() {
      * @param {function} [onFailure] - Called on XHR failure.
      */
     checkUserRegistration: function(email, onSuccess, onFailure) {
-      xhr.ajax({
+      get({
         url: "/wsapi/user_creation_status?email=" + encodeURIComponent(email),
         success: function(status, textStatus, jqXHR) {
-          if (onSuccess) {
-            _.delay(onSuccess, 0, status.status);
-          }
+          if (onSuccess) onSuccess(status.status);
         },
         error: onFailure
       });
@@ -261,9 +285,7 @@ BrowserID.Network = (function() {
           pass: password
         },
         success: function(status, textStatus, jqXHR) {
-          if (onSuccess) {
-            _.delay(onSuccess, 0, status.success);
-          }
+          if (onSuccess) onSuccess(status.success);
         },
         error: onFailure
       });
@@ -294,9 +316,7 @@ BrowserID.Network = (function() {
      */ 
     resetPassword: function(password, onSuccess, onFailure) {
       // XXX fill this in.
-      if (onSuccess) {
-        _.defer(onSuccess);
-      }
+      if (onSuccess) onSuccess();
     },
 
     /**
@@ -311,7 +331,7 @@ BrowserID.Network = (function() {
     changePassword: function(oldPassword, newPassword, onSuccess, onFailure) {
       // XXX fill this in
       if (onSuccess) {
-        _.delay(onSuccess, 0, true);
+        onSuccess(true);
       }
     },
 
@@ -330,9 +350,7 @@ BrowserID.Network = (function() {
           token: token
         },
         success: function(status, textStatus, jqXHR) {
-          if (onSuccess) {
-            _.delay(onSuccess, 0, status.success);
-          }
+          if (onSuccess) onSuccess(status.success);
         },
         error: onFailure
       });
@@ -347,7 +365,7 @@ BrowserID.Network = (function() {
     cancelUser: function(onSuccess, onFailure) {
       post({
         url: "/wsapi/account_cancel",
-        success: createDeferred(onSuccess),
+        success: onSuccess,
         error: onFailure
       });
     },
@@ -368,8 +386,7 @@ BrowserID.Network = (function() {
           site: origin
         },
         success: function(status) {
-          var staged = status.success;
-          _.delay(onSuccess, 0, staged);
+          if (onSuccess) onSuccess(status.success);
         },
         error: onFailure
       });
@@ -383,12 +400,10 @@ BrowserID.Network = (function() {
      * @param {function} [onfailure] - called on xhr failure.
      */
     checkEmailRegistration: function(email, onSuccess, onFailure) {
-      xhr.ajax({
+      get({
         url: "/wsapi/email_addition_status?email=" + encodeURIComponent(email),
         success: function(status, textStatus, jqXHR) {
-          if (onSuccess) {
-            _.delay(onSuccess, 0, status.status);
-          }
+          if (onSuccess) onSuccess(status.status);
         },
         error: onFailure
       });
@@ -404,12 +419,10 @@ BrowserID.Network = (function() {
      * @param {function} [onFailure] - Called on XHR failure.
      */
     emailRegistered: function(email, onSuccess, onFailure) {
-      xhr.ajax({
+      get({
         url: "/wsapi/have_email?email=" + encodeURIComponent(email),
         success: function(data, textStatus, xhr) {
-          if(onSuccess) {
-            _.delay(onSuccess, 0, data.email_known);
-          }
+          if(onSuccess) onSuccess(data.email_known);
         },
         error: onFailure
       });
@@ -429,9 +442,7 @@ BrowserID.Network = (function() {
           email: email
         },
         success: function(status, textStatus, jqXHR) {
-          if (onSuccess) {
-            _.delay(onSuccess, 0, status.success);
-          }
+          if (onSuccess) onSuccess(status.success);
         },
         error: onFailure
       });
@@ -441,15 +452,15 @@ BrowserID.Network = (function() {
      * Certify the public key for the email address.
      * @method certKey
      */
-    certKey: function(email, pubkey, onSuccess, onError) {
+    certKey: function(email, pubkey, onSuccess, onFailure) {
       post({
         url: "/wsapi/cert_key",
         data: {
           email: email,
           pubkey: pubkey.serialize()
         },
-        success: createDeferred(onSuccess),
-        error: onError
+        success: onSuccess,
+        error: onFailure
       });
     },
 
@@ -458,10 +469,9 @@ BrowserID.Network = (function() {
      * @method listEmails
      */
     listEmails: function(onSuccess, onFailure) {
-      xhr.ajax({
-        type: "GET",
+      get({
         url: "/wsapi/list_emails",
-        success: createDeferred(onSuccess),
+        success: onSuccess,
         error: onFailure
       });
     },
diff --git a/browserid/static/dialog/test/qunit/resources/network_unit_test.js b/browserid/static/dialog/test/qunit/resources/network_unit_test.js
index 840084d99456c32b871ec7862d82339dfdab6658..684e267be41866405f460efc684c4f4f8e8de8b8 100644
--- a/browserid/static/dialog/test/qunit/resources/network_unit_test.js
+++ b/browserid/static/dialog/test/qunit/resources/network_unit_test.js
@@ -69,7 +69,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     results: {
       "get /wsapi/session_context valid": contextInfo,   
       "get /wsapi/session_context invalid": contextInfo,
-      // We are going to test for ajax errors for session_context using 
+      // We are going to test for XHR failures for session_context using 
       // call to serverTime.  We are going to use the flag contextAjaxError
       "get /wsapi/session_context ajaxError": contextInfo, 
       "get /wsapi/session_context contextAjaxError": undefined,  
@@ -185,14 +185,28 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
+/*
+  wrappedAsyncTest("authenticate with XHR failure, checking whether application is notified", function() {
+    xhr.useResult("ajaxError");
 
-  wrappedAsyncTest("authenticate with ajax error after context already setup", function() {
+    OpenAjax.hub.subscribe("xhrError", function() {
+      ok(true, "xhr error notified application");
+      wrappedStart();
+    });
+
+    network.authenticate("testuser@testuser.com", "ajaxError");
+    
+    stop();
+  });
+*/
+  wrappedAsyncTest("authenticate with XHR failure after context already setup", function() {
     xhr.useResult("ajaxError");
+    
     network.authenticate("testuser@testuser.com", "ajaxError", function onSuccess(authenticated) {
-      ok(false, "ajax error should never pass");
+      ok(false, "XHR failure should never pass");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should never pass");
+      ok(true, "XHR failure should never pass");
       wrappedStart();
     });
 
@@ -229,7 +243,7 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
   });
 
 
-  wrappedAsyncTest("checkAuth with ajax error", function() {
+  wrappedAsyncTest("checkAuth with XHR failure", function() {
     xhr.useResult("ajaxError");
     contextInfo.authenticated = false;
 
@@ -258,14 +272,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
   });
 
 
-  wrappedAsyncTest("logout with ajax error", function() {
+  wrappedAsyncTest("logout with XHR failure", function() {
     xhr.useResult("ajaxError");
 
     network.logout(function onSuccess() {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
@@ -296,13 +310,13 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("complete_email_addition with ajax error", function() {
+  wrappedAsyncTest("complete_email_addition with XHR failure", function() {
     xhr.useResult("ajaxError");
     network.completeEmailRegistration("goodtoken", function onSuccess(proven) {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
@@ -332,14 +346,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("createUser with ajax error", function() {
+  wrappedAsyncTest("createUser with XHR failure", function() {
     xhr.useResult("ajaxError");
 
     network.createUser("validuser", "origin", function onSuccess(created) {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
@@ -374,14 +388,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("checkUserRegistration with ajax error", function() {
+  wrappedAsyncTest("checkUserRegistration with XHR failure", function() {
     xhr.useResult("ajaxError");
 
     network.checkUserRegistration("address", function(status) {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
@@ -414,14 +428,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("completeUserRegistration with ajax error", function() {
+  wrappedAsyncTest("completeUserRegistration with XHR failure", function() {
     xhr.useResult("ajaxError");
 
     network.completeUserRegistration("token", "password", function(registered) {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
@@ -455,14 +469,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("cancelUser with ajax error", function() {
+  wrappedAsyncTest("cancelUser with XHR failure", function() {
     xhr.useResult("ajaxError");
 
     network.cancelUser(function() {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
@@ -497,14 +511,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("emailRegistered with ajax error", function() {
+  wrappedAsyncTest("emailRegistered with XHR failure", function() {
     xhr.useResult("ajaxError");
 
     network.emailRegistered("address", function(taken) {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
@@ -537,14 +551,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("addEmail with ajax error", function() {
+  wrappedAsyncTest("addEmail with XHR failure", function() {
     xhr.useResult("ajaxError");
 
     network.addEmail("address", "origin", function onSuccess(added) {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
@@ -579,14 +593,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("checkEmailRegistration with ajax error", function() {
+  wrappedAsyncTest("checkEmailRegistration with XHR failure", function() {
     xhr.useResult("ajaxError");
 
     network.checkEmailRegistration("address", function(status) {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
@@ -622,14 +636,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("removeEmail with ajax error", function() {
+  wrappedAsyncTest("removeEmail with XHR failure", function() {
     xhr.useResult("ajaxError");
 
     network.removeEmail("invalidemail", function onSuccess() {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
@@ -650,14 +664,14 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("requestPasswordReset with ajax error", function() {
+  wrappedAsyncTest("requestPasswordReset with XHR failure", function() {
     xhr.useResult("ajaxError");
 
     network.requestPasswordReset("address", "origin", function onSuccess() {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
@@ -677,16 +691,16 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("resetPassword with ajax error", function() {
+  wrappedAsyncTest("resetPassword with XHR failure", function() {
     xhr.useResult("ajaxError");
 /*
     the body of this function is not yet written
 
     network.resetPassword("password", function onSuccess() {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
     stop();
@@ -707,16 +721,16 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("changePassword with ajax error", function() {
+  wrappedAsyncTest("changePassword with XHR failure", function() {
     xhr.useResult("ajaxError");
 
     /*
     the body of this function is not yet written.
     network.changePassword("oldpassword", "newpassword", function onSuccess() {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
@@ -743,17 +757,27 @@ steal.plugins("jquery", "funcunit/qunit").then("/dialog/resources/network", func
     stop();
   });
 
-  wrappedAsyncTest("serverTime with ajax error before context has been setup", function() {
+  wrappedAsyncTest("serverTime with XHR failure before context has been setup", function() {
     xhr.useResult("contextAjaxError");
 
     network.serverTime(function onSuccess(time) {
-      ok(false, "ajax error should never call success");
+      ok(false, "XHR failure should never call success");
       wrappedStart();
     }, function onFailure() {
-      ok(true, "ajax error should always call failure");
+      ok(true, "XHR failure should always call failure");
       wrappedStart();
     });
 
     stop();
   });
+
+  wrappedAsyncTest("body offline message triggers offline message", function() {
+    OpenAjax.hub.subscribe("offline", function() {
+      ok(true, "offline event caught and application notified");
+      start();
+    });
+
+    $("body").trigger("offline");
+    stop();
+  });
 });