How can I encrypt data in the Web Push API?

Here’s a service worker push handler I wrote:

self.addEventListener("push", (ev) => {

I then pushed an event to it with:

curl -H 'Authorization: key=MY_SERVER_KEY' -H 'Content-Type: application/json' -d '{"data":{"method":"notification","title":"There's a new post!"},"to":"DEVICE_TOKEN"}'

This is wrong for a whole bunch of reasons! I got an error in my service worker:

Uncaught TypeError: Cannot read property 'title' of null

Why is data == null? It turns out that Google have decided this push data is too juicy to be seen by intermediary push services like GCM/FCM (“wait, I thought Google wanted to see my data?”). Instead, Google have decided on an encryption scheme for such data, so that push services can never read it. The user agent (browser) generates a private key for each new push subscription. The corresponding public key is given to the web application, which the web application then passes to some server. The server then encrypts any data with this public key, and the user agent (browser) decrypts it before passing it on to the web application.

In full, when doing PushManager.subscribe, my browser gives me an object like this:

  "endpoint": "",
  "expirationTime": null,
  "keys": {

The keys.p256dh is the asymmetric public key. The keys.auth is apparently a “shared authentication secret”, “to allow the client to authenticate that the message was sent by a trusted server”. (This seems to duplicate the purpose of the device token and/or capability URL?) The web application is supposed to serialize all of the PushSubscription (e.g. with JSON.stringify), and send this to the application server.

Now, on the application server, it’s more complicated than a curl command. Instead, Google have made this web-push library for Node.js (nope, there’s not one for Go).

npm install web-push --save
const webpush = require('web-push');
const pushSubscription = {endpoint:"...",keys:{p256dh:"...",auth:"..."}};
webpush.sendNotification(pushSubscription, JSON.stringify({method:"notification",title:"There's a new post!"}));

Then, on the server worker push event, there is an object has a data, but it’s a PushMessageData. This has a method .json() to parse it from JSON into an ordinary object. So our service worker push handler looks like this:

self.addEventListener("push", (ev) => {
  let data =;
  if (data.method === "notification") {
    self.registration.showNotification(data.title, data.options);
  } else {
    console.log("Received push with unknown method: ", data, ev);
I just released Vidrio, a free app for macOS and Windows to make your screen-sharing awesomely holographic. Vidrio shows your webcam video on your screen, just like a mirror. Then you just share or record your screen with Zoom, QuickTime, or any other app. Vidrio makes your presentations effortlessly engaging, showing your gestures, gazes, and expressions. #1 on Product Hunt. Available for macOS and Windows.

With Vidrio

With generic competitor

More by Jim

Tagged . All content copyright James Fisher 2017. This post is not associated with my employer. Found an error? Edit this page.