PWA Guide

Web Push notifications

How web push subscriptions work, what VAPID is for, and how iOS Safari differs from Android Chrome.

All materials

1. Flow in one minute

  1. Ask for notification permission after a clear user action.
  2. Subscribe with PushManager + your VAPID public key.
  3. Send the subscription JSON to your backend.
  4. Server sends a push message signed with VAPID.
  5. Service Worker receives push and shows a notification.

2. Subscribe example

const permission = await Notification.requestPermission();
if (permission !== 'granted') return;

const reg = await navigator.serviceWorker.ready;
const sub = await reg.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: urlBase64ToUint8Array(PUBLIC_VAPID_KEY),
});
await fetch('/api/push/subscribe', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(sub),
});

3. SW push handler

self.addEventListener('push', (event) => {
  const data = event.data ? event.data.json() : {};
  event.waitUntil(self.registration.showNotification(data.title || 'Update', {
    body: data.body || '',
    icon: '/icons/icon-192.png',
    data: { url: data.url || '/' },
  }));
});

self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  const url = event.notification.data?.url || '/';
  event.waitUntil(clients.openWindow(url));
});

4. Chrome vs iOS Safari

TopicChrome / AndroidSafari / iOS
Web PushMatureSupported on newer iOS, with limits
Home Screen installHelpful, not always requiredUsually required for reliable web push
Permission UXPrompt availableStricter; ask in context
Background deliveryGenerally reliableMore constrained by system power rules

5. Practical rules

  • Never ask for push on first paint.
  • Explain value before the browser permission dialog.
  • Store subscriptions per device and handle expiration.
  • Keep payloads small; put details behind a URL open.