The Push API
The Push API
Section titled “The Push API”The Push API enables your server to send messages to a user’s device even when the user is not actively using your app — no open tab required. Under the hood, the browser maintains a persistent connection to a push service (operated by the browser vendor). Your server talks to that push service, and the push service delivers the message to the user’s device where your service worker picks it up.
How push works end-to-end
Section titled “How push works end-to-end”sequenceDiagram
participant Page as Page (JS)
participant SW as Service Worker
participant PS as Push Service (browser vendor)
participant Srv as Your Server
Page->>SW: navigator.serviceWorker.ready
Page->>PS: pushManager.subscribe({ applicationServerKey })
PS-->>Page: PushSubscription (endpoint + keys)
Page->>Srv: POST /subscribe (subscription JSON)
Note over Srv: Stores subscription in DB
Srv->>PS: POST endpoint with encrypted payload + VAPID auth
PS->>SW: push event with data
SW->>SW: event.waitUntil(showNotification(...)) Step 1 — Generate VAPID keys
Section titled “Step 1 — Generate VAPID keys”VAPID (Voluntary Application Server Identification) keys authenticate your server with the push service. Generate them once and store them securely:
# Using the web-push npm packagenpx web-push generate-vapid-keysThis outputs a public key (share with the browser) and a private key (keep only on your server — never expose it).
Step 2 — Subscribe from the page
Section titled “Step 2 — Subscribe from the page”// main.js — page codeasync function subscribeToPush() { const registration = await navigator.serviceWorker.ready;
// Convert your VAPID public key (base64url) to a Uint8Array const applicationServerKey = urlBase64ToUint8Array(VAPID_PUBLIC_KEY);
const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, // required: every push must show a notification applicationServerKey, });
// Send the subscription object to your backend await fetch('/api/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(subscription), });
console.log('Subscribed:', subscription.endpoint);}
function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); const rawData = atob(base64); return Uint8Array.from([...rawData].map((c) => c.charCodeAt(0)));}userVisibleOnly: true is mandatory — browsers require that every incoming push message results in a visible notification.
Step 3 — Store the subscription on your server
Section titled “Step 3 — Store the subscription on your server”Your server receives the subscription JSON (containing endpoint, keys.auth, keys.p256dh) and stores it in a database keyed by user.
Step 4 — Send a push from your server
Section titled “Step 4 — Send a push from your server”// server.js (Node.js with web-push)import webpush from 'web-push';
webpush.setVapidDetails( process.env.VAPID_PUBLIC_KEY, process.env.VAPID_PRIVATE_KEY,);
async function sendPush(subscription, payload) { await webpush.sendNotification(subscription, JSON.stringify(payload));}
// ExamplesendPush(storedSubscription, { title: 'Your order shipped!', body: 'It will arrive by Thursday.',});Step 5 — Handle the push event in the service worker
Section titled “Step 5 — Handle the push event in the service worker”// sw.js — service workerself.addEventListener('push', (event) => { const data = event.data ? event.data.json() : { title: 'Update', body: '' };
event.waitUntil( self.registration.showNotification(data.title, { body: data.body, icon: '/icons/icon-192.png', badge: '/icons/badge-72.png', data, }) );});event.waitUntil() keeps the SW alive until the notification is shown. If the SW terminates before showNotification resolves, the notification may be lost.
Checking existing subscriptions
Section titled “Checking existing subscriptions”Before subscribing, check whether a subscription already exists to avoid duplicates:
const existing = await registration.pushManager.getSubscription();if (existing) { console.log('Already subscribed:', existing.endpoint); return existing;}Unsubscribing
Section titled “Unsubscribing”const subscription = await registration.pushManager.getSubscription();if (subscription) { await subscription.unsubscribe(); // Also notify your server to remove the stored subscription await fetch('/api/unsubscribe', { method: 'POST', body: JSON.stringify({ endpoint: subscription.endpoint }), });}Code-only lesson: Push requires a backend server with VAPID keys, a database, and a push service. The examples above are complete and accurate but cannot run in the in-browser playground. Copy them into a real project — for example a Node.js server with
web-pushand any frontend framework.