diff --git a/example/rp/index.html b/example/rp/index.html
index d7221a471106f09f097ccfb6ae4fc9f00cae49c0..99fc8351b0a09efdc6d982ee900ea8b2ca151b48 100644
--- a/example/rp/index.html
+++ b/example/rp/index.html
@@ -17,7 +17,7 @@ a:link, a:visited { font-style: italic; text-decoration: none; color: #008; }
 a:hover { border-bottom: 2px solid black ; }
 .title { font-size: 2em; font-weight: bold; text-align: center; margin: 1.5em auto 1.5em auto; }
 .intro { font-size: 1.2em;  }
-.specify { font-size: 1.1em; padding-top: 2em; }
+.specify, .session { font-size: 1.1em; padding-top: 2em; }
 body div { width: 600px; margin: auto; }
 
 pre {
@@ -57,7 +57,7 @@ pre {
 </div>
 
 <div class="specify">
-  <p>What flavor of assertion would you like?</p>
+  <p><b>What flavor of assertion would you like?</b></p>
   <ul>
     <li>
       <input type="checkbox" id="privacy">
@@ -74,22 +74,31 @@ pre {
     <button class="logout">logout</button>
 </div>
 
+<div class="session">
+  <p><b>Care to simulate a session?</b></p>
+  <p>If you enter an email address or 'null' here, upon reload this value will
+     be passed to .watch() as the first parameter.  This lets you test things like
+     assertion generation suppression when the site and browser agree on who is logged in.
+  </p>
+  <input type="text" id="loggedInUser" width="80">
+  <button class="update_session">Update "session"</button>
+</div>
+
 <div class="loginEvents">
-  <h2>login events</h2>
-  <pre> ... login events ... </pre>
+  <h2>logins</h2>
+  <pre> ... </pre>
 </div>
 
 <div class="logoutEvents">
-  <h2>logout events</h2>
-  <pre> ... logout events ... </pre>
+  <h2>logouts</h2>
+  <pre> ... </pre>
 </div>
 
-<div class="loginCanceledEvents">
-  <h2>loginCanceled events</h2>
-  <pre> ... loginCanceled events ... </pre>
+<div class="readiness">
+  <h2>readiness</h2>
+  <pre> ... </pre>
 </div>
 
-
 </body>
 
 <script src="jquery-min.js"></script>
@@ -102,6 +111,8 @@ function loggit() {
   } catch(e) {}
 }
 
+var serial = 1;
+
 // a function to check an assertion against the server
 function checkAssertion(assertion) {
   $.ajax({
@@ -113,7 +124,8 @@ function checkAssertion(assertion) {
       audience: window.location.protocol + "//" + window.location.host
     },
     success: function(data, textStatus, jqXHR) {
-      $(".loginEvents > pre").text(JSON.stringify(data, null, 4));
+      var old = $(".loginEvents > pre").text() + "\n";
+      $(".loginEvents > pre").text(old + JSON.stringify(data, null, 4));
     },
     error: function(jqXHR, textStatus, errorThrown) {
       var resp = jqXHR.responseText ? JSON.parse(jqXHR.responseText) : errorThrown;
@@ -122,21 +134,26 @@ function checkAssertion(assertion) {
   });
 };
 
-navigator.id.addEventListener('login', function(event) {
-  loggit("login event");
-  checkAssertion(event.assertion);
-});
-
-navigator.id.addEventListener('logout', function(event) {
-  loggit("logout event");
-  var txt = 'got event at ' + (new Date).toString();
-  $(".logoutEvents > pre").text(txt);
-});
-
-navigator.id.addEventListener('loginCanceled', function(event) {
-  loggit("loginCanceled");
-  var txt = 'got event at ' + (new Date).toString();
-  $(".loginCanceledEvents > pre").text(txt);
+navigator.id.experimental.watch({
+  email: (localStorage.loggedInUser === 'null') ? null : localStorage.loggedInUser,
+  onready: function () {
+    loggit("onready");
+    var txt = serial++ + ' navigator.id ready at ' + (new Date).toString();
+    $(".readiness > pre").text(txt);
+
+  },
+  onlogin: function (assertion) {
+    loggit("onlogin");
+    var txt = serial++ + ' got assertion at ' + (new Date).toString();
+    $(".loginEvents > pre").text(txt);
+
+    checkAssertion(assertion);
+  },
+  onlogout: function () {
+    loggit("onlogout");
+    var txt = serial++ + ' logout callback invoked at ' + (new Date).toString();
+    $(".logoutEvents > pre").text(txt);
+  }
 });
 
 $(document).ready(function() {
@@ -146,14 +163,26 @@ $(document).ready(function() {
     var requiredEmail = $.trim($('#requiredEmail').val());
     if (!requiredEmail.length) requiredEmail = undefined;
 
-    navigator.id.request({
+    $(".specify button.assertion").attr('disabled', 'true');
+
+    navigator.id.experimental.request({
       privacyURL: $('#privacy').attr('checked') ? "/privacy.html" : undefined,
       tosURL: $('#tos').attr('checked') ? "/TOS.html" : undefined,
-      requiredEmail: requiredEmail
+      requiredEmail: requiredEmail,
+      onclose: function() {
+        loggit("onclose");
+        $(".specify button.assertion").removeAttr('disabled');
+      }
     });
   });
 
   $(".specify button.logout").click(navigator.id.logout);
+
+  $(".session button.update_session").click(function() {
+    localStorage.loggedInUser = $.trim($('#loggedInUser').val());
+    $(".session input").fadeOut(100).fadeIn(350);
+  });
+  $('#loggedInUser').val(localStorage.loggedInUser ? localStorage.loggedInUser : "");
 });
 
 </script>
diff --git a/resources/static/communication_iframe/start.js b/resources/static/communication_iframe/start.js
index ec70a5b08b97e023640a6b26a45f3233645976be..57ce36dcaca52ba1d78fc3db41208e89371444ec 100644
--- a/resources/static/communication_iframe/start.js
+++ b/resources/static/communication_iframe/start.js
@@ -70,9 +70,12 @@
   });
 
   chan.bind("loaded", function(trans, params) {
+    trans.delayReturn(true);
     setRemoteOrigin(trans.origin);
-    checkAndEmit(watchState);
-    trans.complete();
+    checkAndEmit(function() {
+      watchState();
+      trans.complete();
+    });
   });
 
   chan.bind("logout", function(trans, params) {
diff --git a/resources/static/include_js/include.js b/resources/static/include_js/include.js
index 01a67fe39fd6e13c5c74aa6ea2997deb292bd5ab..e8255e426f2a38475ab2f319faea771a172a2608 100644
--- a/resources/static/include_js/include.js
+++ b/resources/static/include_js/include.js
@@ -770,12 +770,13 @@
             try {
               var d = JSON.parse(e.data);
               if (d.a === 'ready') messageTarget.postMessage(req, origin);
-              else if (d.a === 'error') cb(d.d);
-              else if (d.a === 'response') {
+              else if (d.a === 'error') {
+                if (cb) { cb(d.d); cb = null; }
+              } else if (d.a === 'response') {
                 removeListener(window, 'message', onMessage);
                 removeListener(window, 'unload', cleanup);
                 cleanup();
-                cb(null, d.d);
+                if (cb) { cb(null, d.d); cb = null; }
               }
             } catch(e) { }
           };
@@ -936,11 +937,11 @@
 
     var w;
 
-    // table of registered event listeners
-    var listeners = {
-      login: [ ],
-      logout: [ ],
-      loginCanceled: [ ]
+    // table of registered observers
+    var observers = {
+      login: null,
+      logout: null,
+      ready: null
     };
 
     var compatMode = undefined;
@@ -951,30 +952,8 @@
 
       if (compatMode === undefined) compatMode = requiredMode;
       else if (compatMode != requiredMode) {
-        throw "you cannot combine browserid event APIs with navigator.id.getVerifiedEmail() or navigator.id.get()" +
-              "this site should instead use navigator.id.request() and the browserid event API";
-      }
-    }
-
-    function emitEvent(type, params) {
-      if (listeners[type]) {
-        var evt = document.createEvent('Event');
-        evt.initEvent(type, true, true);
-        // XXX: we should probably implement .stopImmediatePropagation()
-        if (params) {
-          for (var k in params) {
-            if (params.hasOwnProperty(k)) {
-              evt[k] = params[k];
-            }
-          }
-        }
-        for (var i = 0; i < listeners[type].length; i++) {
-          try {
-            listeners[type][i](evt);
-          } catch(e) {
-            // XXX: what shall we do when an exception is raised by an event handler?
-          }
-        }
+        throw "you cannot combine the navigator.id.watch() API with navigator.id.getVerifiedEmail() or navigator.id.get()" +
+              "this site should instead use navigator.id.request() and navigator.id.watch()";
       }
     }
 
@@ -996,102 +975,50 @@
             // once the channel is set up, we'll fire a loaded message.  this is the
             // cutoff point where we'll say if 'setLoggedInUser' was not called before
             // this point, then it wont be called (XXX: optimize and improve me)
-            commChan.call({ method: 'loaded', success: function(){}, error: function() {} });
+            commChan.call({
+              method: 'loaded',
+              success: function(){
+                if (observers.ready) observers.ready();
+              }, error: function() {
+              }
+            });
           }
         });
 
         commChan.bind('logout', function(trans, params) {
-          emitEvent('logout');
+          if (observers.logout) observers.logout();
         });
 
         commChan.bind('login', function(trans, params) {
-          emitEvent('login', { assertion: params });
+          if (observers.login) observers.login(params);
         });
       }
     }
 
-    function internalAddEventListener(type, listener) {
-      // add event to listeners table if it's not there already
-      if (!listeners[type]) throw "unsupported event type: '" + type + "'";
+    function internalWatch(options) {
+      if (typeof options !== 'object') return;
 
-      // is the function already registered?
-      for (var i = 0; i < listeners[type].length; i++) {
-        if (listeners[type][i] === listener) return;
+      if (options.onlogin && typeof options.onlogin !== 'function' ||
+          options.onlogout && typeof options.onlogout !== 'function' ||
+          options.onready && typeof options.onready !== 'function')
+      {
+        throw "non-function where function expected in parameters to navigator.id.watch()";
       }
-      listeners[type].push(listener);
-    }
 
-    navigator.id.addEventListener = function(type, listener) {
-      checkCompat(false);
+      observers.login = options.onlogin || null;
+      observers.logout = options.onlogout || null;
+      observers.ready = options.onready || null;
 
-      // allocate iframe if it is not allocated
       _open_hidden_iframe();
-      internalAddEventListener(type,listener);
-    };
 
-    function internalRemoveEventListener(type, listener ) {
-      // remove event from listeners table
-      var i;
-      for (i = 0; i < listeners[type].length; i++) {
-        if (listeners[type][i] === listener) break;
-      }
-      if (i < listeners[type][i].length) {
-        listeners[type].splice(i, 1);
+      if (typeof options.email !== 'undefined') {
+        commChan.notify({
+          method: 'loggedInUser',
+          params: options.email
+        });
       }
     }
 
-    navigator.id.removeEventListener = function(type, listener/*, useCapture */) {
-      checkCompat(false);
-      internalRemoveEventListener(type, listener);
-    };
-
-    navigator.id.logout = function() {
-      checkCompat(false);
-
-      // allocate iframe if it is not allocated
-      _open_hidden_iframe();
-
-      // send logout message
-      commChan.notify({ method: 'logout' });
-    };
-
-    navigator.id.setLoggedInUser = function(email) {
-      checkCompat(false);
-
-      // 1. allocate iframe if it is not allocated
-      _open_hidden_iframe();
-
-      // 2. send a "loggedInUser" message to iframe
-      commChan.notify({ method: 'loggedInUser', params: email });
-    };
-
-    // backwards compatibility function
-    navigator.id.get = function(callback, options) {
-      checkCompat(true);
-
-      if (options && options.silent) {
-        if (callback) setTimeout(function() { callback(null); }, 0);
-      } else {
-        function handleEvent(e) {
-          internalRemoveEventListener('login', handleEvent);
-          callback((e && e.assertion) ? e.assertion : null);
-        }
-        internalAddEventListener('login', handleEvent);
-        internalRequest(options);
-      }
-    };
-
-    // backwards compatibility function
-    navigator.id.getVerifiedEmail = function(callback) {
-      checkCompat(true);
-      navigator.id.get(callback);
-    };
-
-    navigator.id.request = function(options) {
-      checkCompat(false);
-      return internalRequest(options);
-    };
-
     function internalRequest(options) {
       // focus an existing window
       if (w) {
@@ -1147,12 +1074,71 @@
 
         // clear the window handle
         w = undefined;
-        if (!err && r && r.assertion) emitEvent('login', { assertion: r.assertion });
-        else emitEvent('loginCanceled');
+        if (!err && r && r.assertion) {
+          try {
+            if (observers.login) observers.login(r.assertion);
+          } catch(e) {
+            // client's observer threw an exception
+          }
+        }
+
+        // complete
+        if (options && options.onclose) options.onclose();
+        delete options.onclose;
       });
     };
 
-    navigator.id._shimmed = true;
+    navigator.id = {
+      // The experimental API, not yet final
+      experimental: {
+        request: function(options) {
+          checkCompat(false);
+          return internalRequest(options);
+        },
+        watch: function(options) {
+          checkCompat(false);
+          internalWatch(options);
+        }
+      },
+      // logout from the current website
+      logout: function() {
+        // allocate iframe if it is not allocated
+        _open_hidden_iframe();
+        // send logout message
+        commChan.notify({ method: 'logout' });
+      },
+      // get an assertion
+      get: function(callback, options) {
+        checkCompat(true);
+        internalWatch({
+          onlogin: function(assertion) {
+            if (callback) {
+              callback(assertion);
+              callback = null
+            }
+          }
+        });
+        options.oncomplete = function() {
+          if (callback) {
+            callback(null);
+            callback = null;
+          }
+          internalWatch({});
+        };
+        if (options && options.silent) {
+          if (callback) setTimeout(function() { callback(null); }, 0);
+        } else {
+          internalRequest(options);
+        }
+      },
+      // backwards compatibility with old API
+      getVerifiedEmail: function(callback) {
+        checkCompat(true);
+        navigator.id.get(callback);
+      },
+      // required for forwards compatibility with native implementations
+      _shimmed: true
+    };
   }
 }());
 
diff --git a/resources/static/test/cases/include.js b/resources/static/test/cases/include.js
index 42aa6c6562488a4dfba94cba7b438641c8d0ba02..2273cc25c93da6a3116c33a716eff262be9d8be4 100644
--- a/resources/static/test/cases/include.js
+++ b/resources/static/test/cases/include.js
@@ -15,15 +15,21 @@
   test("expected public API functions available", function() {
     _.each([
       "get",
-      "request",
-      "setLoggedInUser",
-      "logout",
-      "addEventListener",
-      "removeEventListener"
+      "getVerifiedEmail",
+      "logout"
     ], function(item, index) {
       equal(typeof navigator.id[ item ], "function", "navigator.id." + item + " is available");
     });
   });
 
+  test("expected experimental API function is available", function() {
+    _.each([
+      "request",
+      "watch"
+    ], function(item, index) {
+      equal(typeof navigator.id.experimental[ item ], "function", "navigator.id." + item + " is available");
+    });
+  });
+
 }());