Subscribing users to web push notifications on your website is not enough: you also need to manage and renew their push subscriptions properly over time, otherwise you will lose subscribers.
Note: If you use Pushpad all the management and renewal of the push subscriptions is performed automatically by our service and you don’t have to worry about it. Read below if you are interested in the technical details or if you are building your own solution from scratch.
When a user visits your website and subscribes to your notifications, you get a secret endpoint (URL), which allows you to deliver notifications to that specific device.
However the endpoint may expire or change after some time (as we have already described) and you will get an error like this when you try to send a notification to it:
410 push subscription has unsubscribed or expired
This can happen mainly for two reasons:
- the user has unsubscribed from your notifications by changing the browser preferences
- the push subscription has been replaced / renewed automatically by the browser and you must update it in your application server.
In the first case, when the user has unsubscribed, you can’t do much… When you try to send a notification and you get 410 Gone, you should simply remove that subscription from your server.
For the second case, the user is still subscribed and wants to receive your notifications… he has simply changed address (i.e. he has a new push subscription endpoint). This means that you must listen for changes of the endpoint and update it on your application server when it changes.
In order to detect changes of the endpoint and update it on your server before it returns the 410 error code you can use the pushsubscriptionchange
event in your service worker.
This is the code that we use for Pushpad for example:
self.addEventListener('pushsubscriptionchange', function(event) {
event.waitUntil(
fetch('https://pushpad.xyz/pushsubscriptionchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
old_endpoint: event.oldSubscription ? event.oldSubscription.endpoint : null,
new_endpoint: event.newSubscription ? event.newSubscription.endpoint : null,
new_p256dh: event.newSubscription ? event.newSubscription.toJSON().keys.p256dh : null,
new_auth: event.newSubscription ? event.newSubscription.toJSON().keys.auth : null
})
})
);
});
Then on the server you need to find the old endpoint and replace it with the new one (you also need to update the p256dh and auth). In this way you can also retain all the customer data associated to the push subscription.
If you want more details you can read the Subscription Refreshes section in the standard. You can also find a description of web push errors, including 410 Gone, on the Mozilla autopush documentation.
Hello, is this still working ?
The pushsubscriptionchange event does not seem to be supported anymore by Chrome. We are facing this problem with WebPush suddenly expiring and we don’t know how to do ..
The “pushsubscriptionchange” event is defined by the Push API standard (it’s still present in the current version):
https://www.w3.org/TR/push-api/#dfn-pushsubscriptionchange
You should support it in your web app to avoid losing subscribers due to expired subscriptions.
In any case each browser is free to replace the subscriptions periodically (and trigger “pushsubscriptionchange”) or to keep them forever without expiration.
Note that if you are using Pushpad for notifications you don’t need to do anything: everything is already managed properly by Pushpad.
Another (normal) reason why you can see some expired subscriptions is that there are some users that unsubscribe from your notifications (e.g. they block the notifications from browser preferences).
I’m currently using a custom code in a normal script to refresh the subscription. Obviously, I’m loosing users.
Now I have two questions for you, are you sure your solution works on Chrome for mobile? (my userbase is 99% on Chrome for mobile)
Should I move all push related code from ordinary scripts to serviceWorker?
Thank you
You should not refresh the subscription from your JavaScript code in general. That is not useful and if your code is not perfect you may lose some users.
In this post we are talking about something different: the browser replaces the subscription automatically and your code (in the Service Worker) should manage that event properly and send the new subscription to your server. This process is initiated by the browser, not by your code.
Thanks for getting back on this. A look at this page https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/pushsubscriptionchange_event shows that it is no longer supported except by Firefox.
It seems that PushPad keeps stats on this. Are you still seeing many hits on the “pushsubscriptionchange” endpoint you configured in the sw? We have some customers using our service and afaik, they don’t want their subscription to expire but it is and it’s causing us/them problems.
We don’t collect specific stats, but we see the requests in the server logs.
Note that even if you have configured everything properly (see the original post and the other comments) you can still see some “expired” subscriptions and that is completely normal. You get a “410 Gone” status, not only when a browser replaces a subscription, but also when a user revokes the permission for push notifications or when he clears the browser data, etc.
Is this solution supported by chrome? In the case of chrome, what is a workaround for this?
Currently (July 2022) the data from caniuse.com suggests that the event is not supported by the current version of Chrome (this may change in the future versions):
https://caniuse.com/mdn-api_serviceworkerglobalscope_pushsubscriptionchange_event
See also the related issues in the Chromium bug tracker:
https://bugs.chromium.org/p/chromium/issues/list?q=pushsubscriptionchange
As a workaround you can get the current push subscription and send it to your server each time that the user visits your website (once per session).
Finally note that some browsers don’t set an expiration on the push subscriptions (in that case the PushSubscription.expirationTime is null).
Hello,
Reading through this excerpt in your blog article : “In any case each browser is free to replace the subscriptions periodically (and trigger “pushsubscriptionchange”) or to keep them forever without expiration.”, I have 2 questions :
1. Are you suggesting that we “replace the subscriptions periodically” when the user visits our website ?
2. How can we possibly keep a Subscriber alive forever without expiration ?
It’s the browser that may replace the subscription periodically, it’s not something that your website can control.
You should listen for the “pushsubscriptionchange” event, as described above. Note that if you use Pushpad the “pushsubscriptionchange” event is already managed automatically.
Otherwise, each time that a user visits your website, once per session, you can get the current subscription (e.g. PushManager.getSubscription()) and send it to your server. For example if you use Pushpad Javascript SDK you can use this code:
That depends on the browser, you can’t control that.
You can also check the PushSubscription.expirationTime and see if it’s null: in that case the subscription doesn’t have an expiration. However there are some rare circumstances where the subscription can be replaced by the browser even if it doesn’t have an expiration (e.g. if the browser push service loses some subscriptions and the browser needs to generate a new one).