diff --git a/lib/browserid/views.js b/lib/browserid/views.js
index 412609c48bcb3ee5314cf3ed8f4023d68c9687fc..f1d582c4642fc08d37682c87b8c910c1346429f1 100644
--- a/lib/browserid/views.js
+++ b/lib/browserid/views.js
@@ -55,6 +55,13 @@ exports.setup = function(app) {
     res.render('signup.ejs', {title: 'Sign Up', fullpage: false});
   });
 
+  app.get("/idp_auth_complete", function(req, res) {
+    res.render('idp_auth_complete.ejs', {
+      title: 'Sign In Complete',
+      fullpage: false
+    });
+  });
+
   app.get("/forgot", function(req, res) {
     res.render('forgot.ejs', {title: 'Forgot Password', fullpage: false, email: req.query.email});
   });
diff --git a/resources/static/pages/page_helpers.js b/resources/static/pages/page_helpers.js
index 0ea6e2cf1ee93f85469363bd0f807042195e917a..7f016b399388b66397eac9acb4f908c993403fcb 100644
--- a/resources/static/pages/page_helpers.js
+++ b/resources/static/pages/page_helpers.js
@@ -87,14 +87,18 @@ BrowserID.PageHelpers = (function() {
       return decodeURIComponent(results[1].replace(/\+/g, " "));
   }
 
+  function showFailure(error, info, callback) {
+    info = $.extend(info || {}, { action: error, dialog: false });
+    bid.Screens.error.show("error", info);
+    $("#errorBackground").stop().fadeIn();
+    $("#error").stop().fadeIn();
+
+    callback && callback(false);
+  }
+
   function getFailure(error, callback) {
     return function onFailure(info) {
-      info = $.extend(info, { action: error, dialog: false });
-      bid.Screens.error.show("error", info);
-      $("#errorBackground").stop().fadeIn();
-      $("#error").stop().fadeIn();
-
-      callback && callback(false);
+      showFailure(error, info, callback);
     }
   }
 
@@ -143,6 +147,15 @@ BrowserID.PageHelpers = (function() {
     clearStoredEmail: clearStoredEmail,
     getStoredEmail: getStoredEmail,
     getParameterByName: getParameterByName,
+    /**
+     * shows a failure screen immediately
+     * @method showFailure
+     */
+    showFailure: showFailure,
+    /**
+     * get a function to show an error screen when function is called.
+     * @method getFailure
+     */
     getFailure: getFailure,
     replaceInputsWithNotice: replaceInputsWithNotice,
     replaceFormWithNotice: replaceFormWithNotice,
diff --git a/resources/static/pages/signup.js b/resources/static/pages/signup.js
index 5f31d21e193a58b7f742917f12e45dd2849b7488..1a1853470c829f8cce8ba3b2ca75679d66713c27 100644
--- a/resources/static/pages/signup.js
+++ b/resources/static/pages/signup.js
@@ -47,7 +47,7 @@ BrowserID.signUp = (function() {
       tooltip = BrowserID.Tooltip,
       ANIMATION_SPEED = 250,
       storedEmail = pageHelpers,
-      win = window,
+      winchan = window.WinChan,
       verifyEmail,
       verifyURL;
 
@@ -55,13 +55,16 @@ BrowserID.signUp = (function() {
       $(selector).fadeIn(ANIMATION_SPEED);
     }
 
-    function verifyWithPrimary(oncomplete) {
+    function authWithPrimary(oncomplete) {
       if(!(verifyEmail && verifyURL)) {
         throw "cannot verify with primary without an email address and URL"
       }
 
-      var url = verifyURL + "?email=" + encodeURIComponent(verifyEmail) +
-                            "&return_to=https://browserid.org/sign_in_complete";
+      var url = helpers.toURL(verifyURL, {
+          email: verifyEmail,
+          return_to: "https://browserid.org/idp_auth_complete"
+      });
+
       // XXX: we should use winchan (and send user to a page that redirects to primary)!
       // we should:
       // 1. build a page that we host and we open with winchan
@@ -74,10 +77,67 @@ BrowserID.signUp = (function() {
       // 8. we get notification that the interaction is complete in main page and try to
       //    silently provision again!  success means the users is signed up, failure
       //    means there was an auth problem.  they can try again?
-      win.open(url, "_moz_primary_verification", "width=700,height=375");
+      var win = winchan.open({
+        url: url,
+        // This is the relay that will be used when the IdP redirects to sign_in_complete
+        relay_url: "https://browserid.org/relay",
+        window_features: "width=700,height=375"
+      }, primaryAuthComplete);
       oncomplete && oncomplete();
     }
 
+    function primaryAuthComplete(error, result, oncomplete) {
+      // XXX once we get our winchan shit figured out, remove the false &&
+      // below.
+      if(false && error) {
+        pageHelpers.showFailure(errors.primaryAuthentication, error, oncomplete);
+      }
+      else {
+        // hey ho, the user is authenticated, re-try the submit.
+        createUser(verifyEmail, oncomplete);
+      }
+    }
+
+    function createUser(email, oncomplete) {
+      function complete(status) {
+        oncomplete && oncomplete(status);
+      }
+
+      user.createUser(email, function onComplete(status, info) {
+        switch(status) {
+          case "secondary.already_added":
+            $('#registeredEmail').html(email);
+            showNotice(".alreadyRegistered");
+            complete(false);
+            break;
+          case "secondary.verify":
+            pageHelpers.showEmailSent(complete);
+            break;
+          case "secondary.could_not_add":
+            tooltip.showTooltip("#could_not_add");
+            complete(false);
+            break;
+          case "primary.already_added":
+            // XXX Is this status possible?
+            break;
+          case "primary.verified":
+            pageHelpers.replaceFormWithNotice("#congrats", complete.bind(null, true));
+            break;
+          case "primary.verify":
+            verifyEmail = email;
+            verifyURL = info.auth;
+            dom.setInner("#primary_email", email);
+            pageHelpers.replaceInputsWithNotice("#primary_verify", complete.bind(null, false));
+            break;
+          case "primary.could_not_add":
+            // XXX Can this happen?
+            break;
+          default:
+            break;
+        }
+      }, pageHelpers.getFailure(errors.createUser, complete));
+    }
+
     function submit(oncomplete) {
       var email = helpers.getAndValidateEmail("#email");
 
@@ -86,39 +146,7 @@ BrowserID.signUp = (function() {
       }
 
       if (email) {
-        user.createUser(email, function onComplete(status, info) {
-          switch(status) {
-            case "secondary.already_added":
-              $('#registeredEmail').html(email);
-              showNotice(".alreadyRegistered");
-              complete(false);
-              break;
-            case "secondary.verify":
-              pageHelpers.showEmailSent(complete);
-              break;
-            case "secondary.could_not_add":
-              tooltip.showTooltip("#could_not_add");
-              complete(false);
-              break;
-            case "primary.already_added":
-              // XXX Is this status possible?
-              break;
-            case "primary.verified":
-              pageHelpers.replaceFormWithNotice("#congrats", complete.bind(null, true));
-              break;
-            case "primary.verify":
-              verifyEmail = email;
-              verifyURL = info.auth;
-              dom.setInner("#primary_email", email);
-              pageHelpers.replaceInputsWithNotice("#primary_verify", complete.bind(null, false));
-              break;
-            case "primary.could_not_add":
-              // XXX Can this happen?
-              break;
-            default:
-              break;
-          }
-        }, pageHelpers.getFailure(errors.createUser, oncomplete));
+        createUser(email, complete);
       }
       else {
         complete(false);
@@ -136,8 +164,8 @@ BrowserID.signUp = (function() {
     function init(config) {
       config = config || {};
 
-      if(config.window) {
-        win = config.window;
+      if(config.winchan) {
+        winchan = config.winchan;
       }
 
       $("form input[autofocus]").focus();
@@ -147,7 +175,7 @@ BrowserID.signUp = (function() {
       dom.bindEvent("#email", "keyup", onEmailKeyUp);
       dom.bindEvent("form", "submit", cancelEvent(submit));
       dom.bindEvent("#back", "click", cancelEvent(back));
-      dom.bindEvent("#verifyWithPrimary", "click", cancelEvent(verifyWithPrimary));
+      dom.bindEvent("#authWithPrimary", "click", cancelEvent(authWithPrimary));
     }
 
     // BEGIN TESTING API
@@ -155,15 +183,16 @@ BrowserID.signUp = (function() {
       dom.unbindEvent("#email", "keyup");
       dom.unbindEvent("form", "submit");
       dom.unbindEvent("#back", "click");
-      dom.unbindEvent("#verifyWithPrimary", "click");
-      win = window;
+      dom.unbindEvent("#authWithPrimary", "click");
+      winchan = window.WinChan;
       verifyEmail = verifyURL = null;
     }
 
     init.submit = submit;
     init.reset = reset;
     init.back = back;
-    init.verifyWithPrimary = verifyWithPrimary;
+    init.authWithPrimary = authWithPrimary;
+    init.primaryAuthComplete = primaryAuthComplete;
     // END TESTING API
 
     return init;
diff --git a/resources/static/shared/error-messages.js b/resources/static/shared/error-messages.js
index dc444e193b35b29526501d9ffefaae83ed91ad4d..13f8b307802fc6be3467c0995a4ecf07df8a1471 100644
--- a/resources/static/shared/error-messages.js
+++ b/resources/static/shared/error-messages.js
@@ -82,8 +82,13 @@ BrowserID.Errors = (function(){
       message: "Unfortunately, BrowserID cannot communicate while offline!"
     },
 
+    primaryAuthentication: {
+      title: "Authenticating with Identity Provider",
+      message: "We had trouble communicating with your email provider, please try again!"
+    },
+
     provisioningPrimary: {
-      title: "Provisioning Primary",
+      title: "Provisioning with Identity Provider",
       message: "We had trouble communicating with your email provider, please try again!"
     },
 
diff --git a/resources/static/shared/helpers.js b/resources/static/shared/helpers.js
index 45060aaed823da357b2d6e3bf5c33c941e8d5390..2105a8d3d701c4093dc973e7737a2b09aba2676a 100644
--- a/resources/static/shared/helpers.js
+++ b/resources/static/shared/helpers.js
@@ -66,6 +66,22 @@
     return password;
   }
 
+  function toURL(base, params) {
+    var url = base,
+        getParams = [];
+
+    for(var key in params) {
+      getParams.push(key + "=" + encodeURIComponent(params[key]));
+    }
+
+    if(getParams.length) {
+      url += "?" + getParams.join("&");
+    }
+
+    return url;
+  }
+
+
   extend(helpers, {
     /**
      * Extend an object with the properties of another object.  Overwrites
@@ -90,7 +106,17 @@
      * @param {string} target - target containing the password
      * @return {string} password if password is valid, null otw.
      */
-    getAndValidatePassword: getAndValidatePassword
+    getAndValidatePassword: getAndValidatePassword,
+
+    /**
+     * Convert a base URL and an object to a URL with GET parameters.  All
+     * keys/values are converted as <key>=encodeURIComponent(<value>)
+     * method @toURL
+     * @param {string} base_url - base url
+     * @param {object} [params] - object to convert to GET parameters.
+     * @returns {string}
+     */
+    toURL: toURL
 
   });
 
diff --git a/resources/static/test/index.html b/resources/static/test/index.html
index feb6f0dafe5c887354a6ab202acd80739dc8a37d..b725c20feb9d743ab12baf97c0e6c2c8b9f09030 100644
--- a/resources/static/test/index.html
+++ b/resources/static/test/index.html
@@ -96,6 +96,7 @@
     <script type="text/javascript" src="qunit/mocks/templates.js"></script>
     <script type="text/javascript" src="qunit/mocks/provisioning.js"></script>
     <script type="text/javascript" src="qunit/mocks/window.js"></script>
+    <script type="text/javascript" src="qunit/mocks/winchan.js"></script>
     <script type="text/javascript" src="/shared/javascript-extensions.js"></script>
     <script type="text/javascript" src="/shared/renderer.js"></script>
     <script type="text/javascript" src="/shared/class.js"></script>
diff --git a/resources/static/test/qunit/mocks/winchan.js b/resources/static/test/qunit/mocks/winchan.js
new file mode 100644
index 0000000000000000000000000000000000000000..2bb3e149160d457523b562b3f6d357dbf441173b
--- /dev/null
+++ b/resources/static/test/qunit/mocks/winchan.js
@@ -0,0 +1,51 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global BrowserID: true */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+BrowserID.Mocks.WinChan = (function() {
+  "use strict";
+
+  function WinChan() { };
+
+  WinChan.prototype = {
+    open: function(params, callback) {
+      this.params = params;
+      this.oncomplete = callback;
+    }
+  };
+
+  return WinChan;
+
+}());
diff --git a/resources/static/test/qunit/pages/page_helpers_unit_test.js b/resources/static/test/qunit/pages/page_helpers_unit_test.js
index 958949b3ad54744f9fcebf31ac3fa6d1a5483be9..67e8ffaa7bbb800ba8443bcbffb5c5e6b67dd717 100644
--- a/resources/static/test/qunit/pages/page_helpers_unit_test.js
+++ b/resources/static/test/qunit/pages/page_helpers_unit_test.js
@@ -39,7 +39,8 @@
 
   var bid = BrowserID,
       pageHelpers = bid.PageHelpers,
-      testHelpers = bid.TestHelpers;
+      testHelpers = bid.TestHelpers,
+      errors = bid.Errors;
 
   module("pages/page_helpers", {
     setup: function() {
@@ -137,6 +138,13 @@
     });
   });
 
+  asyncTest("showFailure shows a failure screen", function() {
+    pageHelpers.showFailure({}, errors.offline, function() {
+      testHelpers.testErrorVisible();
+      start();
+    });
+  });
+
 }());
 
 
diff --git a/resources/static/test/qunit/pages/signup_unit_test.js b/resources/static/test/qunit/pages/signup_unit_test.js
index 12c2f17cdf6dd3079ba058319a3fff43faf17d96..b6fec1620cbad237623f1da7f81f4c9d95e1b8e4 100644
--- a/resources/static/test/qunit/pages/signup_unit_test.js
+++ b/resources/static/test/qunit/pages/signup_unit_test.js
@@ -40,18 +40,18 @@
   var bid = BrowserID,
       network = bid.Network,
       xhr = bid.Mocks.xhr,
-      WindowMock = bid.Mocks.WindowMock,
+      WinChanMock = bid.Mocks.WinChan,
       testHelpers = bid.TestHelpers,
       provisioning = bid.Mocks.Provisioning,
-      win;
+      winchan;
 
   module("pages/signup", {
     setup: function() {
       testHelpers.setup();
 
-      win = new WindowMock();
+      winchan = new WinChanMock();
       bid.signUp({
-        window: win
+        winchan: winchan
       });
     },
     teardown: function() {
@@ -175,16 +175,41 @@
     });
   });
 
-  asyncTest("verifyWithPrimary opens new tab", function() {
+  asyncTest("authWithPrimary opens new tab", function() {
     xhr.useResult("primary");
     $("#email").val("unregistered@testuser.com");
 
     bid.signUp.submit(function(status) {
-      bid.signUp.verifyWithPrimary(function() {
-        equal(win.open_url, "https://auth_url?email=unregistered%40testuser.com", "user directed to authentication URL");
+      bid.signUp.authWithPrimary(function() {
+        ok(winchan.oncomplete, "winchan set up");
         start();
       });
     });
   });
 
+  asyncTest("primaryAuthComplete with error, expect incorrect status", function() {
+    bid.signUp.primaryAuthComplete("error", "", function(status) {
+      equal(status, false, "correct status for could not complete");
+      testHelpers.testErrorVisible();
+      start();
+    });
+  });
+
+  asyncTest("primaryAuthComplete with successful authentication, expect correct status and congrats message", function() {
+    xhr.useResult("primary");
+    $("#email").val("unregistered@testuser.com");
+
+    bid.signUp.submit(function(status) {
+      bid.signUp.authWithPrimary(function() {
+        // In real life the user would now be authenticated.
+        provisioning.setStatus(provisioning.AUTHENTICATED);
+        bid.signUp.primaryAuthComplete(null, "success", function(status) {
+          equal(status, true, "correct status");
+          equal($("#congrats:visible").length, 1, "success notification is visible");
+          start();
+        });
+      });
+    });
+  });
+
 }());
diff --git a/resources/static/test/qunit/shared/helpers_unit_test.js b/resources/static/test/qunit/shared/helpers_unit_test.js
index cd570787a5830a8aefb126fb63e234f819102fd7..8d00c4683cc83cc54ee95fdc190a83c553e1a454 100644
--- a/resources/static/test/qunit/shared/helpers_unit_test.js
+++ b/resources/static/test/qunit/shared/helpers_unit_test.js
@@ -106,4 +106,19 @@
 
     strictEqual(password, null, "invalid target returns null");
   });
+
+  test("toURL with no GET parameters", function() {
+    var url = helpers.toURL("https://browserid.org");
+
+    equal(url, "https://browserid.org", "correct URL without GET parameters");
+  });
+
+  test("toURL with GET parameters", function() {
+    var url = helpers.toURL("https://browserid.org", {
+      email: "testuser@testuser.com",
+      status: "complete"
+    });
+
+    equal(url, "https://browserid.org?email=testuser%40testuser.com&status=complete", "correct URL with GET parameters");
+  });
 }());
diff --git a/resources/views/idp_auth_complete.ejs b/resources/views/idp_auth_complete.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..55a0d3d8a9cdd74dc529d589c97a835e0e15ccc1
--- /dev/null
+++ b/resources/views/idp_auth_complete.ejs
@@ -0,0 +1,9 @@
+  <div id="vAlign" class="disply_always">
+    This window will now close and account creation will continue.
+  </div>
+
+  <script type="text/javascript">
+    WinChan.onOpen(function(origin, params, complete) {
+      complete("success");
+    });
+  </script>
diff --git a/resources/views/layout.ejs b/resources/views/layout.ejs
index fbfa792aebea7c87d49d45c00c55044ad552a8d4..72a2c3fc955b04c4430d97abd813e572bdef7b31 100644
--- a/resources/views/layout.ejs
+++ b/resources/views/layout.ejs
@@ -24,6 +24,7 @@
     <script src="/shared/browserid.js" type="text/javascript"></script>
     <script src="/lib/dom-jquery.js" type="text/javascript"></script>
     <script src="/lib/jschannel.js" type="text/javascript"></script>
+    <script src="/lib/winchan.js" type="text/javascript"></script>
 
     <script src="/shared/templates.js" type="text/javascript"></script>
     <script src="/shared/renderer.js" type="text/javascript"></script>
diff --git a/resources/views/signup.ejs b/resources/views/signup.ejs
index c5642d93232d74e04cb5049bd06637bb3f1e7cd6..124cbbd8dc5d9e0ec2903b2edfc45d2b5dacf324 100644
--- a/resources/views/signup.ejs
+++ b/resources/views/signup.ejs
@@ -34,7 +34,7 @@
                   </p>
 
                   <p>
-                    <button id="verifyWithPrimary">Verify</button>
+                    <button id="authWithPrimary">Verify</button>
                   </p>
                 </li>
             </ul>
diff --git a/scripts/compress.sh b/scripts/compress.sh
index b4d8e22dd2dcaa760a16ce1eed5d6d70dfd7aaba..53f0c063c4a1ccf70217f7327cd840762c97d85c 100755
--- a/scripts/compress.sh
+++ b/scripts/compress.sh
@@ -60,7 +60,7 @@ cat lib/jquery-1.6.2.min.js lib/winchan.js lib/underscore-min.js lib/vepbundle.j
 cat css/common.css dialog/css/popup.css dialog/css/m.css > $BUILD_PATH/dialog.uncompressed.css
 
 # produce the non interactive frame js
-cat lib/jquery-1.6.2.min.js lib/jschannel.js lib/underscore-min.js lib/vepbundle.js shared/javascript-extensions.js shared/browserid.js shared/storage.js shared/network.js shared/user.js communication_iframe/start.js > $BUILD_PATH/communication_iframe.uncompressed.js
+cat lib/jquery-1.6.2.min.js lib/jschannel.js lib/winchan.js lib/underscore-min.js lib/vepbundle.js shared/javascript-extensions.js shared/browserid.js shared/storage.js shared/network.js shared/user.js communication_iframe/start.js > $BUILD_PATH/communication_iframe.uncompressed.js
 
 echo ''
 echo '****Building BrowserID.org HTML, CSS, and JS****'