npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

node-webpush

v1.1.0

Published

A dependency-free Web Push implementation for Node.js

Readme

Node WebPush (Node.js + TypeScript)

npm version License CI Codecov

A dependency-free Web Push implementation for Node.js (TypeScript-first).

This library focuses on standards-compliant payload encryption + VAPID authentication, and produces request details that can be sent with Node’s built-in fetch().

Features

✅ Standards & compatibility

  • RFC 8188: HTTP Encrypted Content Encoding (record-based framing, per-record nonce derivation).
  • RFC 8291: Web Push message encryption (ECDH + auth secret, “WebPush: info” key schedule, aes128gcm).
  • RFC 8292: VAPID (JWT ES256) authentication headers.
  • Supports both encodings:
    • aes128gcm (recommended): modern Web Push encoding (RFC 8291 + RFC 8188).
    • aesgcm (legacy): kept for interoperability with older endpoints.

🔐 Encryption

  • Pure Node.js crypto (no external libs).
  • RFC8188 record framing:
    • delimiter rules (0x01 for non-last, 0x02 for last)
    • optional final-record padding
    • nonce = baseNonce XOR SEQ per record

🪪 VAPID

  • Native ES256 JWT signing using Node’s crypto (JWK-based key objects).
  • Key generation (VAPID.GenerateKeys()).
  • Full validation helpers (VAPID.Validate.*).

🧰 Request building

  • generateRequest() produces { endpoint, init } for fetch(endpoint, init).
  • Sets required headers:
    • TTL, Urgency, optional Topic
    • Content-Encoding, Content-Type, Content-Length
    • Authorization (VAPID or GCM/FCM key when applicable)

⚠️ GCM / FCM edge cases

  • Detects legacy GCM endpoints:
    • Uses Authorization: key=<apiKey> (VAPID not supported on legacy GCM).
  • Supports FCM endpoints:
    • Uses VAPID by default when configured.
    • Can fall back to Authorization: key=<apiKey> if VAPID is disabled and a key is provided.

Installation

npm install node-webpush

TypeScript is supported out of the box (the package emits .d.ts).


Quick start

1) Create a WebPush instance

import {WebPush} from "node-webpush";

const webpush = new WebPush({
    vapid: {
        subject: "mailto:[email protected]",
        publicKey: process.env.VAPID_PUBLIC_KEY!,
        privateKey: process.env.VAPID_PRIVATE_KEY!,
    },
    // Optional: used for legacy GCM/FCM key-based auth fallback
    gcm: {apiKey: process.env.GCM_API_KEY ?? null},
});

2) Send a notification

const subscription = {
    endpoint: "https://push-service.example/...",
    keys: {
        p256dh: "<base64url>",
        auth: "<base64url>",
    },
};

const res = await webpush.notify(subscription, "Hello from WebPush!", {
    TTL: 60,
});

console.log("Status:", res.status);

Generate VAPID keys

import {VAPID} from "node-webpush";

const keys = VAPID.GenerateKeys();
console.log(keys.publicKey);
console.log(keys.privateKey);

You typically store these as environment variables:

  • VAPID_PUBLIC_KEY
  • VAPID_PRIVATE_KEY

API Reference (high level)

new WebPush(config)

type WebPushConfig = {
    vapid: {
        publicKey: string;
        privateKey: string;
        subject: string | URL; // must be https: or mailto:
    };
    gcm?: { apiKey?: string | null };
};

Constructing WebPush validates:

  • VAPID subject format (https: or mailto:)
  • VAPID key sizes and base64url encoding
  • GCM/FCM key if provided (must be non-empty)

webpush.generateRequest(subscription, payload?, options?)

Returns the request parameters to call fetch() yourself.

const {endpoint, init} = webpush.generateRequest(subscription, "payload", {
    TTL: 60,
});

const res = await fetch(endpoint, init);

This is useful if you want to:

  • inspect headers
  • plug into your own HTTP stack
  • retry logic / circuit breakers
  • log request metadata

webpush.notify(subscription, payload?, options?)

Sends the request using fetch().

const res = await webpush.notify(subscription, "hello");

default it return the response even if not successful. It can also throw an error if the push service returns a non-2xx response. This can be enabled by:

import {WebPushError} from "./webpush";

try {
  const res = await webpush.notify(subscription, "hello", {
    throwOnInvalidResponse: true //Add this to the options
  });
} catch (error: WebPushError){
    console.error(error);
    const responseObject  = error.response; //<<-- The resulting response object can still be accessed
}

Throws WebPushError when the push service returns a non-2xx response.

This also contains the response but can be handled in the try-catch logic


Options

type GenerateRequestOptions = {
    headers?: Record<string, string>;

    TTL?: number; // seconds
    urgency?: "very-low" | "low" | "normal" | "high";
    topic?: string; // base64url <= 32 chars

    contentEncoding?: "aes128gcm" | "aesgcm";

    // RFC8188 knobs (primarily for advanced use/testing)
    rs?: number; // default 4096, must be >= 18
    allowMultipleRecords?: boolean; // default false (Web Push wants single record)
    finalRecordPadding?: number; // default 0

    // Override authentication behavior:
    vapidDetails?: WebPushConfig["vapid"] | null;
    gcmAPIKey?: string | null;
};

Notes

  • aes128gcm is recommended for Web Push.
  • For Web Push interoperability, leave allowMultipleRecords at false (default).
  • topic must use URL-safe base64 characters and be <= 32 chars.

Choosing auth method (VAPID vs key)

This library follows typical push-service rules:

  1. Legacy GCM endpoint (https://android.googleapis.com/gcm/send...)
  • Uses Authorization: key=<gcmAPIKey>
  • VAPID is ignored (not supported)
  1. Everything else
  • If vapidDetails is present: uses VAPID
  • Else if endpoint is FCM and a key is present: uses Authorization: key=<gcmAPIKey>

If you want to disable VAPID for a call:

await webpush.notify(subscription, "hello", {
    vapidDetails: null,
    gcmAPIKey: process.env.GCM_API_KEY!,
});

Minimal example with manual fetch

import {WebPush} from "node-webpush";

const webpush = new WebPush({
    vapid: {
        subject: "https://example.com/contact",
        publicKey: process.env.VAPID_PUBLIC_KEY!,
        privateKey: process.env.VAPID_PRIVATE_KEY!,
    },
});

const {endpoint, init} = webpush.generateRequest(subscription, "ping", {
    TTL: 120,
    urgency: "high",
});

console.log(init.headers); // inspect headers

const res = await fetch(endpoint, init);
console.log(res.status);

Error handling

import {WebPush, WebPushError} from "node-webpush";

try {
    await webpush.notify(subscription, "hello");
} catch (e) {
    if (e instanceof WebPushError) {
        console.error("Push service rejected request:", e.response.status);
        console.error("Response body:", await e.response.text());
    } else {
        console.error("Unexpected error:", e);
    }
}

Runtime requirements

  • Node.js with global fetch (Node 18+ recommended).
  • TypeScript target: ES2020 works.

Security notes

  • Keep your VAPID private key secret.
  • Always validate subscriptions server-side before storing or using them.
  • Avoid sending sensitive data in payloads; push payloads can be stored/forwarded by push services.

License

Apache 2.0 See LICENSE