diff --git a/scripts/hab-build-and-push.sh b/scripts/hab-build-and-push.sh
index f9ffb81ad1ef8e780bdc4efd8499eee9c0eab4c4..1f0b129f00851cfae504fc347b17adfdde7ffb3f 100755
--- a/scripts/hab-build-and-push.sh
+++ b/scripts/hab-build-and-push.sh
@@ -31,6 +31,7 @@ npm rebuild node-sass # HACK sometimes node-sass build fails
 npm run build
 mkdir dist/pages
 mv dist/*.html dist/pages
+mv dist/hub.service.js dist/pages
 
 aws s3 sync --acl public-read --cache-control "max-age=31556926" dist/assets "$TARGET_S3_URL/assets"
 aws s3 sync --acl public-read --cache-control "no-cache" --delete dist/pages "$TARGET_S3_URL/pages/latest"
diff --git a/src/hub.js b/src/hub.js
index 4c1f5ca4186d920c339e1703ec7bfcd1069cdd1a..a6cb4084259c25a9d5f50cbab54e60956cde7526 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -293,7 +293,9 @@ document.addEventListener("DOMContentLoaded", async () => {
 
   if (navigator.serviceWorker) {
     navigator.serviceWorker.register("/hub.service.js");
-    navigator.serviceWorker.ready.then(registration => subscriptions.setRegistration(registration));
+    navigator.serviceWorker.ready
+      .then(registration => subscriptions.setRegistration(registration))
+      .catch(() => subscriptions.setRegistrationFailed());
   }
 
   const scene = document.querySelector("a-scene");
@@ -370,7 +372,8 @@ document.addEventListener("DOMContentLoaded", async () => {
     hmd: availableVREntryTypes.isInHMD
   };
 
-  const joinPayload = { profile: store.state.profile, context };
+  const pushSubscriptionEndpoint = await subscriptions.getCurrentEndpoint();
+  const joinPayload = { profile: store.state.profile, push_subscription_endpoint: pushSubscriptionEndpoint, context };
   const hubPhxChannel = socket.channel(`hub:${hubId}`, joinPayload);
 
   hubPhxChannel
@@ -378,6 +381,8 @@ document.addEventListener("DOMContentLoaded", async () => {
     .receive("ok", async data => {
       hubChannel.setPhoenixChannel(hubPhxChannel);
       subscriptions.setHubChannel(hubChannel);
+      subscriptions.setSubscribed(data.subscriptions.web_push);
+      remountUI({ initialIsSubscribed: subscriptions.isSubscribed() });
       await handleHubChannelJoined(entryManager, hubChannel, data);
     })
     .receive("error", res => {
diff --git a/src/subscriptions.js b/src/subscriptions.js
index 1d863bd92c94ac3e9bea22b3b830138c64130a23..d9fef895df19593f91a47e2909dd3dc69208fecf 100644
--- a/src/subscriptions.js
+++ b/src/subscriptions.js
@@ -1,3 +1,5 @@
+import nextTick from "./utils/next-tick.js";
+
 // Manages web push subscriptions
 //
 function urlBase64ToUint8Array(base64String) {
@@ -13,10 +15,6 @@ function urlBase64ToUint8Array(base64String) {
   return outputArray;
 }
 
-// In local storage: Map of sid -> { endpoint: "<endpoint>" }
-// If entry exists, it means there is a subscription to that room, wired to that endpoint.
-const LOCAL_STORE_KEY = "___hubs_subscriptions";
-
 export default class Subscriptions {
   constructor(hubId) {
     this.hubId = hubId;
@@ -30,55 +28,58 @@ export default class Subscriptions {
     this.registration = registration;
   };
 
-  setVapidPublicKey = vapidPublicKey => {
-    this.vapidPublicKey = vapidPublicKey;
+  setRegistrationFailed = () => {
+    this.registration = null;
   };
 
-  getSubscriptionsFromStorage = () => {
-    return JSON.parse(localStorage.getItem(LOCAL_STORE_KEY) || "{}");
+  setVapidPublicKey = vapidPublicKey => {
+    this.vapidPublicKey = vapidPublicKey;
   };
 
-  setSubscriptionsToStorage = subscriptions => {
-    return localStorage.setItem(LOCAL_STORE_KEY, JSON.stringify(subscriptions));
+  setSubscribed = isSubscribed => {
+    this._isSubscribed = isSubscribed;
   };
 
   isSubscribed = () => {
-    if (typeof this._isSubscribed === "undefined") {
-      this._isSubscribed = !!this.getSubscriptionsFromStorage()[this.hubId];
-    }
-
     return this._isSubscribed;
   };
 
-  toggle = async () => {
-    const subscriptions = this.getSubscriptionsFromStorage();
+  getCurrentEndpoint = async () => {
+    if (!navigator.serviceWorker) return null;
 
-    if (this.isSubscribed()) {
-      const subscription = await this.registration.pushManager.getSubscription();
-      this.hubChannel.unsubscribe(subscription);
+    // registration becomes null if failed, non null if registered
+    while (this.registration === undefined) await nextTick();
+    if (!this.registration || !this.registration.pushManager) return null;
+    if ((await this.registration.pushManager.permissionState()) !== "granted") return null;
+    const sub = await this.registration.pushManager.getSubscription();
+    if (!sub) return null;
 
-      delete subscriptions[this.hubId];
+    return sub && sub.endpoint;
+  };
+
+  toggle = async () => {
+    if (this._isSubscribed) {
+      const pushSubscription = await this.registration.pushManager.getSubscription();
+      const res = await this.hubChannel.unsubscribe(pushSubscription);
 
-      if (Object.keys(subscriptions).length === 0 && subscription) {
-        subscription.unsubscribe();
+      if (res && res.has_remaining_subscriptions === false) {
+        pushSubscription.unsubscribe();
       }
     } else {
-      let subscription = await this.registration.pushManager.getSubscription();
+      let pushSubscription = await this.registration.pushManager.getSubscription();
 
-      if (!subscription) {
+      if (!pushSubscription) {
         const convertedVapidKey = urlBase64ToUint8Array(this.vapidPublicKey);
 
-        subscription = await this.registration.pushManager.subscribe({
+        pushSubscription = await this.registration.pushManager.subscribe({
           userVisibleOnly: true,
           applicationServerKey: convertedVapidKey
         });
       }
 
-      subscriptions[this.hubId] = { endpoint: subscription.endpoint };
-      this.hubChannel.subscribe(subscription);
+      this.hubChannel.subscribe(pushSubscription);
     }
 
-    delete this._isSubscribed;
-    this.setSubscriptionsToStorage(subscriptions);
+    this._isSubscribed = !this._isSubscribed;
   };
 }
diff --git a/src/utils/hub-channel.js b/src/utils/hub-channel.js
index 63dcae408bb874d19c9b5dba47d4aa590cc0c56f..81714efc2fe069ce43cc8d0721c4550a2f42667d 100644
--- a/src/utils/hub-channel.js
+++ b/src/utils/hub-channel.js
@@ -96,7 +96,7 @@ export default class HubChannel {
   };
 
   unsubscribe = subscription => {
-    this.channel.push("unsubscribe", { subscription });
+    return new Promise(resolve => this.channel.push("unsubscribe", { subscription }).receive("ok", resolve));
   };
 
   sendMessage = (body, type = "chat") => {