The Push API and its wild unsubscription mechanism

The working draft of the Push API currently doesn’t offer a good way to manage unsubscriptions.

The problems arise in the following scenarios:

  • The user blocks the notifications from the browser preferences
  • The endpoint is replaced because it has expired

You don’t know when the user revokes permission

Currently the event pushsubscriptionchange (tested on Firefox 46) is not called when the user blocks the notifications from the browser preferences.

Consider the following scenario:

  1. The user allows push notifications
  2. The user blocks push notifications from the browser preferences

In this case there is no way to know if the user has unsubscribed. You realize that the endpoint has expired only when you try to push a message to it.

However in many cases you would like to know if an endpoint has expired before sending it a message:

  • For analytics you want to know exactly when the user has unsubscribed
  • For providing a fallback method (like an email) you want to know in advance if the user can be reached using push notifications (usually you use a worker for the delivery job, and it’s difficult to provide the fallback asynchronously after the worker has realized that the endpoint has expired).

UPDATE: As pointed out in the comments, there is currently a discussion on Github about firing an event when the permission is revoked. Now the browser MAY trigger the pushsubscriptionchange event when the subscription is revoked.

You don’t get the expired endpoint as an argument of the callback

Consider the following scenario:

  1. The user subscribes to push notifications
  2. The browser manufacturer server replaces the token with a new one (for its own reasons)

The documentation says that pushsubscriptionchange is triggeredHowever you don’t get the expired endpoint; you can only try to register a new one.

The problem is that the endpoint is usually stored on the server together with other data (the user which it belongs to, tags, etc.). You cannot just create a new endpoint and delete the old one (when you realize that it has expired). You would like update the subscription on the server to preserve the data associated to the endpoint. To do that you need both the old endpoint and the new one.

Currently the only solution to achieve that is to use cookies or local storage to memorize the last endpoint. However you are responsible for keeping them in sync, they can be blocked (e.g. EU Cookie Law scripts) and they can also disappear (e.g. clear history). So it appears as a dirty hack.

It would be reasonable that when the browser manufacturer server invalidates a token, you receive the token that has been invalidated as an argument of pushsubscriptionchange.

The same arguments exposed here would be also valid for the scenario where the user blocks the notifications and invalidates the endpoint (however in this case pushsubscriptionchange is not even called at the moment).

UPDATE: Now this is fixed in the new definition of pushsubscriptionchange. You can use event.oldSubscription and event.newSubscription to get both the endpoints in the same callback: you can then replace the old endpoint with the new one in your application server and keep the attached data (e.g. tags).

Other worries

  • What happens when a user clears the browser cache? Does it affect push subscriptions? Is an event triggered to manage that unsubscription?

4 thoughts on “The Push API and its wild unsubscription mechanism”

  1. I work on the Mozilla Push Server backend, and can possibly help answer a few of your concerns about the endpoint URLs.

    For the most part, it’s a good idea to know who you’re sending information to. Usually, you can associate a given Push Endpoint with a user. One of the better patterns for a site to use for Push is to show users a reason to agree to get push notifications before triggering the notification request. Of course, if you’re offering updates generally and do not have any user info, then all you really have is the endpoint.

    https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API provides pretty good examples on how to integrate push into a site.

    The endpoint is unique. You can either use the entire endpoint (or a hash of it) as a database key to store information like the associated encryption keys, user meta data, or other relevant information. You could also keep it as part of a mapping table allowing you to quickly find the original information if a subscription update returns a 410 error code (indicating that the subscription is no longer valid).

    An invalid subscription is never reused. You will always need to get a new subscription if an old subscription is no longer valid.

    There are a number of reasons why a user may no longer wish to get push updates, and we tend to err in favor of user privacy. This does mean that there are times when your app may simply not get an unsubscription notification. If you do have a user that once had notifications, but now no longer gets them, you could ask the user on their next visit to your site.

    Push notifications are interruptions that the user has agreed to because they believe those interruptions to be worth their time and attention. Much like an email address, the user may simply decide that they had made the wrong choice. It’s a very good idea that sites recognize that a user has granted them a rather high privilege and not squander it.

    Reply
    • Thank your for your response!

      > Much like an email address, the user may simply decide that they had made the wrong choice.

      However I think that endpoints are too much perishable and that it’s very likely that users will loose subscriptions without intention.

      Beside that, which is not what I am criticizing in this article, I think it would be much better to have an unsubscription callback called whenever the token expires (whether it’s the user to block notifications or the push service to delete the endpoint). This would allow the application server to know (a priori) whether a user can be reached with push notifications or not.

      Talking about Pushpad, the current approach of the Push API causes a relevant problem.

      I call “customer server” the application server of a customer of Pushpad. This means that the path of a notification is “customer server” -> Pushpad server -> Mozilla server -> client.

      When the “customer server” sends a notification to Pushpad through the API, in the response (which obviously happens immediately, before the notification is sent) we return which users can be reached. For example the customer server asks Pushpad to send the notification to the user with id=123: we query the database to see if an endpoint associated to the user id 123 exists and we return the response (the user can be reached or not). However due to the current standard if the token has expired recently it still appears in the database, since there isn’t any callback for the “unsubscription” or “endpoint expiration”.
      Safari has a better way to handle that and I think that the Push API should do the same: provide a callback (a callback in the service worker or maybe a webhook like Safari).

      Beside that you cannot provide analytics for unsubscriptions, since you don’t know when users has actually unsubscribed from push notifications.

      Reply
      • Fair points, and something that is being discussed: https://github.com/w3c/push-api/issues/116

        To be honest, it’s still far more reliable to have your customer server monitor for 410/404 and trigger a subscription removal. There are a lot of reasons why your app may never get an unsubscription message. There’s also potential for some fairly bad behavior from apps/sites to pester users who have unsubscribed, but there’s a balance to be struck.

        One thing that makes you a bit special is that you’re also trying to be a push notification proxy. Push is not really meant to support that, rather more direct connections between the App and the App Server. There are, of course, ways around this problem. You could create a similar proxy app for your customers that would manage the front end subscription/unsubscription process and have that send it’s own notifications to your service on user actions. I can think of a few other methods you could use as well, but all of them have their pros and cons.

        Fortunately the spec is still in draft and accepting comments. If you’ve not already done so, I suggest joining in the discussion. https://w3c.github.io/push-api/

        Reply
        • > To be honest, it’s still far more reliable to have your customer server monitor for 410/404 and trigger a subscription removal.

          It’s Pushpad that collects and stores the endpoints and sends push notifications. The customer server never deals directly with the push service (e.g. Mozilla). So it’s not a viable solution.

          > You could create a similar proxy app for your customers that would manage the front end subscription/unsubscription process

          If you are referring to Javascript on the client side Pushpad Pro already does that. Maybe you’ve only seen Pushpad Express which has a completely different behavior. The customer includes some Javascript on his website and the endpoints are sent directly to Pushpad server and stored with some sort of ID provided by the customer. Then that ID is used by the customer to target that users (he doesn’t deal with the endpoints directly). It’s *not* like Mailchimp, where you have a copy of the addresses on your server and another one on Mailchimp. Finally if you are saying to run an app on the customer server, that would vanish the meaning of using a proxy/layer like Pushpad.

          Thank you for posting the relevant discussions on Github!

          Reply

Leave a Comment