🚀🔔Beginners guide to Web Push Notifications using Service Workers

Sep 26, 2018 · 18 min read

Push notifications are very common in the native mobile application platforms like Android & iOS. The are most effective ways to re-engage users to your apps. In this post we will look at how to implement push notifications for the web.

Image for post
Image for post

Notifications can be of two types:

  1. Local Notification: This is generated by your app itself.
  2. Push Notification: This is generated by a server via a push event.

If you are here only for Push notifications, you can safely skip to Step 3: Push Notification


Components needed for Push Notification:

  1. Service Workers
  2. Notification API: This API is used to show a notification prompt to the user just like in case of mobile devices.
  3. Push API: This API is used to get the push message from the server.
Image for post
Image for post

The steps involved in making a local push notification:

  1. Ask for permission from the user using the Notification APIs requestPermission method.
  2. If permission granted :
  • Brilliant! Now we use our service worker to subscribe to a Push Service using Push API.
  • Our Service Worker now listens for push events.
  • On arrival of a push event, service worker awakens and uses the information from the push message to show a notification using notification API.

3. If permission denied : There is nothing much you can do here. So make sure you handle this case in code as well.


The entire code for this blog is available at https://github.com/master-atul/web-push-demo

Step 0: Boilerplate

Let’s create a basic web app (no frameworks).

Now we have a basic project structure. Let’s add some basic code.




To run this project locally, let’s use a simple development server. I will be using http-server npm module.

Inside the frontend directory run

Now open up on browser, you should get:

Image for post
Image for post


To make push notifications work we need Browser Push API and Service Workers. Almost all browsers support Service Workers. Browser support for Push API is also good. Except Safari (Has custom implementation of Push API), all major browsers support it.

Image for post
Image for post

Let’s check support in our code. Change the contents of index.js to:


If the output in the browser console doesn’t show any errors at this point, you are good to go.

Step 1: Register a Service Worker and Get Permission for Notification

To register a service worker that runs in background:
First we add a new js file service.js: This will contain all our service worker code that will run on a separate thread.


Now modify the frontend index.js to:

Let’s run and see:

Image for post
Image for post

Since service workers are registered only once, If you refresh the app using browser refresh button you will not see Hello from service worker again.

You can call register() every time a page loads without concern; the browser will figure out if the service worker is already registered or not and handle it accordingly.

As service workers are event oriented, if we need to do something useful with service workers we will need to listen to events inside it.

One subtlety with the register() method is the location of the service worker file. You’ll notice in this case that the service worker file is at the root of the domain. This means that the service worker’s scope will be the entire origin. In other words, this service worker will receive fetch events for everything on this domain. If we register the service worker file at /example/sw.js, then the service worker would only see fetch events for pages whose URL starts with /example/ (i.e. /example/page1/, /example/page2/).

Debugging Tip for Service workers

  • If you are using Chrome dev tools: You can go to Application Tab > Service Workers. Here you can unregister the service worker and refresh the app again.
    For debugging purposes, I would suggest you enable update on reload checkbox on the top to avoid manual unregister every time you change the service worker file. More detailed guide here.
  • If you are using Firefox: On the Menu bar go to Tools > Web Developer > Service workers or type in the URL bar: about:debugging#workers. This should show you list of all service workers.

Permission for Notification

In order to show a web notification to the user, the web app needs to get permission from the user.
Modify the frontend index.js to:

Let’s refresh and see what happens.

Image for post
Image for post

Note: We are asking for notification permission in the main function since this is a demo app. But ideally, we should not be doing it here as it accounts for bad UX. More details on where you should be calling it in a production app here.

An alternative approach here would be to add a button asking the user to subscribe to notifications and then on click of that button we show the notification prompt.

You can also get the value of permission via

This can be used to hide the button if the user has already answered the notification prompt (if the value is not ‘default’).

Step 2: Local Notification

Now that we have the permission from the user. We can go ahead and try out the notification popup.
To do that modify frontend index.js

Possible values of Notification Option:

Details of each and every property is listed here

Main thread and Service Worker thread

Main thread: The main JS thread that runs when we are browsing a web page with javascript.
Service Worker thread: This is an independent javascript thread which runs on the background and can run even when the page has been closed.

If you notice we haven’t used our service worker till now for any purpose other than just registering it. With respect to Push Notifications, the JS main thread should only be used for all UI related stuff like changing DOM elements or asking a user, the permissions for showing notifications. Notice here that I am displaying the notification from the main thread (index.js). Ideally we will not do this in the main thread. Keep in mind that notifications are only useful for re engaging the user back to your app (ie when the page is not open). If you show notification when the user is on your page, then the user actually gets distracted.

The worker thread (service worker) should be used for waiting for push messages/events and showing the notifications. This is because it is not necessary that the actual page would be open on the browser at the time a push event arrives (meaning main js thread doesn’t exist in this case).

Hence, In practical use cases we will call showNotification from the service.js file. We will do that when we implement remote notifications. Here I just wanted to show you how the showNotification call looks like.

Image for post
Image for post

Step 3: Push Notification


  • We subscribe our service worker to push events from the Push Service of browser.
  • We will send a message to the push service from our backend.
  • Push event from push service containing the message from our backend will arrive at the browser.
  • We use message from the push service to show the notification.
Image for post
Image for post

So What is a Push Service ?

A push service receives a network request, validates it and delivers a push message to the appropriate browser. If the browser is offline, the message is queued until the the browser comes online.

Each browser can use any push service they want, it’s something developers have no control over. This isn’t a problem because every push service expects the same API call. Meaning you don’t have to care who the push service is. You just need to make sure that your API call is valid.

To know more: Read up here

Before we proceed

Let’s clean up our code a bit as per recommendations above. Make sure our files look like this now:




Let’s run this: You should now see a button on the page saying ‘Ask Permission’. Clicking on it would call our main function.

Listen to Remote Notification

We will now only focus on the service worker file from now on unless specified otherwise.
To listen/subscribe to remote push messages we need the browser web app to register to the push service.


Now, unregister the service workers and refresh the web app. Click on Ask for permission button.

In Firefox console you should see:

The value of subscription will be null if the web app has not subscribed to the push service.

In Chrome console you would see an error:

This is because, there are two option parameters applicationServerKey (also known as VAPID public key) and userVisibleOnly which are optional in Firefox but are required parameters in chrome.

userVisibleOnly: You need to send userVisibleOnly as true always in chrome. This parameter restricts the developer to use push messages for notifications. That is the developer will not be able use the push messages to send data to server silently without showing a notification. Currently it has to be set to true otherwise you get a permission denied error. In essence, silent push messages are not supported at the moment due to privacy and security concerns.

VAPID: Voluntary Application Server Identification for Web Push is a spec that allows a backend server(your application server) to identify itself to the Push Service(browser specific service). It is a security measure that prevents anyone else from sending messages to an application’s users.

How does VAPID keys enable security ?

In short:

  1. You generate a set of private and public keys (VAPID keys) at the application server(for example:your backend NodeJS server).
  2. Public key will be sent to the push service when the frontend app tries to subscribe to the push service. Now the push service knows the public key from your application server(backend). The subscribe method will return a unique endpoint for you frontend web app. You will store this unique endpoint at the backend application server.
  3. From the application server, you will hit an API request to the unique push service endpoint that you just saved. In this API request you will sign some information in the Authorization header with the private key. This allows the push service to verify that the request came from the correct application server.
  4. After validating the header, the push service will send the message to the frontend web app.

PS: To get the value of push subscription you can also call (in your service worker file)

Generating VAPID Keys

To generate a set of private and public VAPID keys:

  • npm install -g web-push
  • web-push generate-vapid-keys

This should print out something like this:


You need to only generate this once. Keep your private key safe. Public key can be stored on frontend web app.

Now let’s get back to service.js

Now refresh and unregister/re-register the service worker. Click on “Ask Permission”. You should see in your console.

This is quite similar to the one we received from the Firefox subscription as well. Notice that fcm.googleapis.com (firebase) is the push service used by google chrome while Firefox uses updates.push.services.mozilla.com. The Web Push Protocol standardises the Push API. This enables each browser to use the push service it deems fit as long as it conforms to the same API as mentioned in the spec.

Now once we receive the value of subscription, we need to save it in our backend server (application server). To do so we should create an API end point on our application server that will take in the subscription and store it in the database/cache. Let’s for now consider that the API to hit is POST http://localhost:3000/save-subscription with request body containing the entire subscription object. We will make this API on the backend server soon.

So let’s change the service.js file to add functionality to save the subscription.

Next step is to save subscription at the backend and then use the subscription to send push messages to the frontend app via the push service. But before that, we will also need to listen to the push event from the backend on our frontend app.

Listen to Push event

In your service.js add

Time for backend (NodeJS)

Let’s create a basic express js project.

Now create a backend index.js file

Run the node server as shown below and open up localhost:4000 on the browser

You should see something like this:

Image for post
Image for post

Saving subscription on the backend: Let’s create our save-subscription endpoint on the express server. All we need to do is retrieve the subscription from frontend and stored it somewhere safely in the backend. We will need this subscription later to send push messages to the user’s browser.

Make changes to your backend nodejs index.js file :

Now we have saved the subscription at the backend, next step is to finally send a push message to the frontend.

Generate Remote Notification

To generate a push message we will use a npm library web-push which will help us encrypt the message, setting the correct parameters, legacy support for browsers etc. If you need to figure out how to do this in languages other than Javascript(NodeJS), you can take a look at the web-push source code.

In your backend project, install the npm module web-push locally.

In the backend index.js add the following:

webpush.setVapidDetails takes three arguments.

  • First argument needs to be either a URL or a mailto email address.
  • Second argument is the VAPID public key we generated earlier.
  • Third argument is the VAPID private key.

To test this out let’s add another route /send-notification to our NodeJS express server.
The complete backend index.js file would look something like this.

Let’s try it out!

Image for post
Image for post

Let’s open up in the browser

Make sure you start from clean slate. Unregister service workers, clear up cache, etc. Now hit ask permission and you should see a console message success. Which means our push subscription has been saved in backend.

Now open up the backend route http://localhost:4000/send-notification in another tab in your browser (since it is a GET API). You should see hello world push message on the console of the frontend app.

Image for post
Image for post
Image for post
Image for post

Adding final touches: Show push messages as a notification

Open up your frontend service.js and add the following:

The final service.js file looks like this:

Now let’s run it one last time. Again, unregister everything, clean up cache and reopen the browser tab. Do the same procedure as before.

Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

Wohoo !! 🚀 Now you can create web apps that can spam people too 🤣



The entire code for this blog is available at https://github.com/master-atul/web-push-demo

This story was originally published at https://blog.atulr.com/web-notifications on 25th September 2018

We build tools to help business grow — this is how we do it.


Written by


iZettle Engineering

Written by

https://blog.atulr.com : I have moved from medium to my personal blog here. Follow me on twitter : https://twitter.com/a7ulr

iZettle Engineering

We build tools to help business grow — this is how we do it.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store