Web Push: subscriptions, permissions, and delivery
In one line: Web Push pairs the Push API (a service-worker subscription tied to a
browser push service) with the Notifications API (showing a message). Your server
encrypts a payload, POSTs it to the subscription endpoint, the push service wakes your
service worker’s push event, and you call self.registration.showNotification(). On
iOS/Safari, this only works for a PWA the user has installed to the Home Screen.
The end-to-end flow
Section titled “The end-to-end flow”- Subscribe. With an active service worker registration, call
registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey }), passing your VAPID public key. The browser returns aPushSubscriptioncontaining anendpointURL plusp256dhandauthkeys. - Store. Send the subscription JSON to your server and persist it per user/device.
- Send. Your server encrypts the payload (RFC 8291), signs a VAPID JWT (RFC 8292), and
POSTs to the subscription’s
endpoint— the push service operated by the browser vendor. - Receive. The push service delivers to the device, which wakes the service worker and
fires a
pushevent even when no page is open. - Show. In the
pushhandler you callshowNotification(); a click firesnotificationclick, where you focus or open the right window.
Permission model
Section titled “Permission model”Notification.requestPermission() (or the implicit prompt during subscribe()) must be
triggered by a user gesture — a click or tap, not on page load. Ask in context, after
the user does something that explains why notifications help. Permission has three states:
default— not yet decided; you may prompt once, tactfully.granted— you may subscribe and show notifications.denied— terminal in practice; you cannot re-prompt, so never spam the request.
Because userVisibleOnly: true is required, every push you send must surface a visible
notification — silent background push is not permitted.
Handling push and clicks in the service worker
Section titled “Handling push and clicks in the service worker”self.addEventListener('push', (event) => { const 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(); event.waitUntil(clients.openWindow(event.notification.data.url));});Wrap async work in event.waitUntil() so the service worker stays alive until the
notification is shown or the click is handled.
Browser & ecosystem support
Section titled “Browser & ecosystem support”| Browser / Platform | Support | Since | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Android) | ✅ yes | 50 | high | ref | — |
| Chrome (Desktop) | ✅ yes | 50 | high | ref | — |
| Edge (Desktop) | ✅ yes | 17 | high | ref | — |
| Safari (iOS) | ⚠️ partial | 16.4 | medium | ref | Only for home-screen-installed web apps; requires user-gesture permission and Web Push via APNs. |
| Safari (macOS) | ✅ yes | 16 | medium | ref | — |
| Firefox (Desktop) | ✅ yes | 44 | high | ref | — |
| Samsung Internet | ✅ yes | 5.0 | high | ref | — |
Ecosystem & commercial policy
| Entity | Type | Context | Status | Sponsored | Notes |
|---|---|---|---|---|---|
| Apple Push (APNs) | delivery_policy | iOS | ⚠️ partial | No | iOS Web Push requires the user to add the app to the Home Screen first. |
Decision framework
Section titled “Decision framework”| Decision question | Recommended action | Rationale |
|---|---|---|
| When do I request permission? | After a user gesture that gives context. | Browsers ignore non-gesture prompts; cold prompts get denied. |
| What auth do I use to send? | VAPID (application server keys). | Identifies your server to the push service; no per-vendor accounts needed. |
| Do I need an encrypted payload? | Yes, for any payload (RFC 8291). | Push services only relay ciphertext; the browser decrypts with p256dh/auth. |
| Need to reach iOS users? | Require Home Screen install first. | iOS/Safari only delivers web push to installed PWAs. |
| A send returns 404/410? | Delete that subscription. | The endpoint is gone or expired; stop sending to it. |
Practical checklist
Section titled “Practical checklist”- Register a service worker and confirm it is
activebefore subscribing. - Request permission from a real user gesture, in context — never on load.
- Subscribe with
userVisibleOnly: trueand your VAPIDapplicationServerKey. - Persist the full
PushSubscription(endpoint +p256dh+auth) server-side. - Encrypt payloads (RFC 8291) and sign VAPID JWTs (RFC 8292) when sending.
- Always call
showNotification()in thepushhandler (required byuserVisibleOnly). - Handle
notificationclickto focus an existing client or open the target URL. - Prune subscriptions on
404/410responses from the push service. - For iOS/Safari, gate the prompt behind an installed (Home Screen) PWA.