web-push: sending encrypted Web Push messages from a Node.js backend
Web Push library for Node.js
At a glance
- What is it?
- The web-push package handles the Web Push Protocol, payload encryption and VAPID signing so a Node.js server can deliver browser notifications. It is small, but the API surface hides several decisions you have to make yourself.
- Who is it for?
- Adopt web-push if your backend is Node.js and you already have a browser PushSubscription object to send to; it is the piece that turns that object plus a payload into a correctly encrypted, VAPID-signed request. Do not adopt it if you are not running Node.js, or if you expect it to manage subscriptions, retries or delivery analytics, because it does none of that.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem web-push solves, and who actually needs it
A browser push message is not an HTTP POST you can write by hand. The Web Push Protocol defines how the request is addressed and authorised, and if you want to attach data, Message Encryption for Web Push defines how that data is encrypted for the specific subscriber. Doing both correctly means implementing key agreement, authentication secrets and signature generation. web-push packages that work into a Node.js module.
The audience is narrow and specific: backend engineers running a Node.js service that already collects PushSubscription objects from a service worker. The README frames the common case as an application server using a GCM API key and VAPID keys, and notes that legacy support for browsers relying on GCM is handled as well. If your push sending happens in a language other than JavaScript, this library is not the tool, and the README does not point to ports in other languages.
What sendNotification actually does with your subscription and payload
The mechanism is visible in the two inputs. The first argument is the JSON.stringify output of a browser PushSubscription: an endpoint URL plus a keys object holding p256dh and auth. The second is the payload, either a string or a Node Buffer. The README is explicit that payload encryption requires both p256dh and auth to be present, which is the practical consequence of the encryption step: without the subscriber's public key and auth secret there is nothing to encrypt to, so a subscription missing those fields can only receive an empty wake-up push.
Options carry the rest. vapidDetails holds subject, publicKey and privateKey and follows the VAPID spec. contentEncoding selects the encoding, with aes128gcm as the default and aesgcm available. TTL is expressed in seconds and controls how long the push service retains the message, four weeks by default. urgency defaults to normal, and topic is capped at 32 characters from the URL or filename-safe Base64 character sets. There is also a timeout option, and the README spends a paragraph correcting a common misreading of it: it is a socket timeout, not a deadline for the full response, so a response split across packets arriving under the limit will not trip it. Once the socket timeout fires, the library aborts the request and rejects the promise.
Two smaller details matter in production. Options passed to sendNotification override anything set globally, so a per-request gcmAPIKey wins over setGCMAPIKey(). And the README states that sendNotification works without a payload, and without a GCM API key or VAPID keys, if the push service supports it. That is a real capability, but it is also where teams get confused later when they add a payload and discover the encryption requirement.
Installing web-push and sending a first notification
Installation is one npm command, as the README gives it:
npm install web-push --saveVAPID keys should be generated only once, per the README, and the module exposes a generator for that. The CLI can produce the same pair as JSON, which is convenient when you want to paste the values into environment configuration:
web-push generate-vapid-keys --jsonThe output is an object with publicKey and privateKey. Store the private key as a secret; the public key is the one your front end passes to the browser when subscribing:
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: '<Your Public Key from generateVAPIDKeys()>'
});On the server, set the VAPID details once at startup and send. The README's example uses require, and the package.json declares "type": "module" with src/index.js as main, so check how your own project resolves the import before copying it verbatim:
const webpush = require('web-push');
const vapidKeys = webpush.generateVAPIDKeys();
webpush.setVapidDetails(
'mailto:[email protected]',
vapidKeys.publicKey,
vapidKeys.privateKey
);
webpush.sendNotification(pushSubscription, 'Your Push Payload Text');If you would rather not write code first, the globally installed CLI sends a message directly. The README shows the flags: --endpoint, --key, --auth, --payload, --encoding, --ttl, --vapid-subject, --vapid-pubkey, --vapid-pvtkey, --proxy and --gcm-api-key. A successful call returns without error; what the reader should look for is the absence of a rejection, since the browser-side notification is what confirms delivery.
Where web-push stops: no subscription storage, no retry policy
The library sends one notification to one subscription. It does not store subscriptions, does not track which endpoints have expired, and does not retry failed sends. Those are application concerns, and the README does not document a rollback or retry mechanism because there is none to document.
The failure mode that catches people is an expired or revoked subscription. The push service rejects the request, the promise rejects, and it is on your code to decide whether that endpoint should be deleted. Nothing in the module tells you that a given rejection means the subscription is permanently gone rather than temporarily unreachable.
A second boundary is the socket timeout semantics described above. If you set timeout expecting an overall request deadline, you will not get one, and a slow push service can hold the request open longer than you planned. Teams that treat timeout as a budget for the whole send will be surprised.
Finally, the package is JavaScript only. The README does not describe bindings or a wire protocol you could reimplement from this repository alone. If your sending service is written in Go, Python or Java, this module is the wrong tool regardless of how well it fits the protocol.
web-push versus calling the push service directly
The alternative is not another Node library so much as the raw HTTP path: build the request to the endpoint yourself, sign it with your VAPID key pair, and encrypt the payload per the Message Encryption spec. That is exactly the work the README's Why section describes, and it is why the module exists. Going direct buys you control over the HTTP client, the agent and the exact headers, and it removes a dependency tree that currently includes asn1.js, http_ece, https-proxy-agent, jws and minimist.
The cost of going direct is that you own the encryption and the signature. Those are the parts where a subtle mistake produces a request the push service rejects with little explanation. The module also covers legacy GCM delivery, which a hand-rolled implementation would have to reproduce. For most Node services the trade is not close: the dependency list is short and the protocol details are not where you want to spend a sprint. For a service that already has a hardened HTTP stack and a strong reason to avoid extra packages, the direct route is defensible, but the encryption code is now yours to test.
Maintenance, licence and the cost of staying current
The repository is not archived, and its last push was on 2026-09-21, so the codebase is being touched. That is not the same as a stable release cadence: the most recent releases listed are v3.6.5, v3.6.4 and v3.6.3, all dated 2023-08-29, while package.json declares version 3.6.7. The gap between the release tags and the version in package.json is worth understanding before you pin a dependency, because it suggests work has landed without a corresponding tagged release.
Upgrade cost is low in the ordinary case. The public surface is a handful of functions and one CLI, and the protocol it implements is fixed by spec rather than by product direction. The engines field requires Node >= 16, so an upgrade path that keeps you on a supported Node line is the main constraint.
Licensing needs care. The repository has a LICENSE file, but the hosting metadata reports NOASSERTION, which means the platform could not map the file to a known identifier. package.json states "license": "MPL-2.0". Those two signals disagree, and MPL-2.0 is a file-level copyleft licence with obligations that differ from permissive licences. Read the LICENSE file itself and have your own process confirm what it means for your distribution model; this is not something to settle from a metadata field.
Editorial conclusion
Adopt web-push if your backend is Node.js and you already have a browser PushSubscription object to send to; it is the piece that turns that object plus a payload into a correctly encrypted, VAPID-signed request. Do not adopt it if you are not running Node.js, or if you expect it to manage subscriptions, retries or delivery analytics, because it does none of that. Before committing, run web-push generate-vapid-keys --json once, confirm your Node version satisfies the engines field (>= 16), and read the licence file in the repository rather than trusting the NOASSERTION label on the hosting page.
Frequently asked questions
What is web-push and what does it do?
It is a Node.js library for sending Web Push Protocol messages from a backend, including encryption of the payload and VAPID signing. The README describes it as making it easy to send messages while also handling legacy support for browsers relying on GCM.
How do I send web push notifications with web-push?
Install the package, set your VAPID details with setVapidDetails, then call sendNotification with the PushSubscription object and an optional payload string or Buffer. The README notes that encrypting a payload requires the subscription to include p256dh and auth keys.
How do I generate VAPID keys for web-push?
The module exposes generateVAPIDKeys(), and the README says these keys should be generated only once. The CLI can produce them as JSON with web-push generate-vapid-keys --json, returning a publicKey and privateKey pair.
How does web-push deliver a notification to the browser?
It sends an HTTP request to the endpoint from the browser PushSubscription, encrypting any payload for that subscriber's p256dh and auth values and signing the request with your VAPID keys. Options such as TTL, urgency, topic and contentEncoding control how the push service handles it.
Can web-push send a notification without a payload?
Yes. The README states that sendNotification does not require a payload, and that the method will work without a GCM API key or VAPID keys if the push service supports it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/web-push-libs-web-push)