Web Push Notifications: How They Work and How to Use Them Well
How web push notifications work, from service workers and VAPID to real browser support including iOS, plus the permission UX and metrics that decide results.
Web push notifications are the only marketing channel where one bad design decision can permanently lock you out of a customer. Ask for permission at the wrong moment, the visitor clicks Block, and that browser is closed to you forever. No re-prompt, no second campaign, no win-back email. That asymmetry is why the mechanism is worth understanding before you write a single message.
What Web Push Notifications Are
A web push notification is a message sent from your server to a subscriber’s browser, displayed by the operating system’s notification centre, and delivered even when your site is closed. That last part is the whole point: unlike an on-page popup, web push reaches someone who is not currently looking at your website.
It is built from three web platform APIs working together, and the split between them explains most of the channel’s behaviour: the Service Worker API, the Push API, and the Notifications API.
How Web Push Works Under the Hood
The service worker
A service worker is a JavaScript worker that acts as a proxy between your web app, the browser and the network. It runs on its own thread, has no DOM access, and keeps existing after the page that registered it closes. That persistence is what lets it receive a push message and display a notification when nobody has your tab open. Service workers only run in secure contexts, which means HTTPS, with http://localhost treated as secure for development.
The subscription: an endpoint plus two keys
Once a service worker is active, the page calls registration.pushManager.subscribe(). The browser talks to its vendor’s push service and returns a PushSubscription containing:
endpoint, a unique capability URL that the push service accepts messages onkeys.p256dh, an Elliptic Curve Diffie-Hellman public key on the P-256 curvekeys.auth, an authentication secret
Your server stores all three and treats the endpoint as a secret, because anyone holding it can send to that subscriber. The keys exist because payloads are encrypted end to end. RFC 8291 specifies how: an ECDH exchange on P-256 establishes a shared secret, HKDF derives keys from it, and the payload is sealed with AES-128-GCM under the aes128gcm content encoding. The push service relays ciphertext it cannot read.
The push service
You do not send push messages directly to a device. You send them to a push service run by the browser vendor: Google’s FCM endpoints for Chrome, Mozilla’s autopush for Firefox, Apple’s push service for Safari. RFC 8030, “Generic Event Delivery Using HTTP Push”, defines the protocol. Your server POSTs to the subscription endpoint, and the push service handles the hard parts of mobile delivery: one battery-efficient connection to the device, queueing while offline, waking the browser when a message arrives. This is also why delivery cannot be guaranteed. If the device is off for long enough, messages expire according to their TTL and are dropped.
VAPID: proving who is sending
The endpoint being a secret is thin security. RFC 8292 adds Voluntary Application Server Identification, or VAPID, so a push service can tell which application server a message came from.
You generate an ECDSA key pair on the NIST P-256 curve once. The public key goes into applicationServerKey when the browser subscribes, binding the subscription to your server. For every push, your server sends a JWT signed with the matching private key using ES256, carrying an aud claim for the push service origin, an exp claim no more than 24 hours out, and optionally a sub claim with contact details. The push service verifies the signature, so a stolen endpoint alone is no longer enough to spam your subscribers.
The delivery path, end to end
- The page registers a service worker and, after permission is granted, calls
subscribe()with your VAPID public key. - Your server stores the returned endpoint and keys against the subscriber record.
- To send, your server encrypts the payload with
p256dhandauth, signs a VAPID JWT, and POSTs to the endpoint. - The push service authenticates the request and delivers the encrypted message.
- The browser wakes the service worker with a
pushevent, which decrypts the payload and callsServiceWorkerRegistration.showNotification(). - A click fires
notificationclickin the service worker, where you open the destination URL.
Why the permission model is so strict
Look at what a subscription grants: a background process that runs without your site being open, plus the ability to draw on the operating system’s notification surface. Browsers therefore gate it behind an explicit, per-origin, user-granted permission, and most require that the request follow a genuine user gesture.
A second constraint surprises people. Chrome and Edge require userVisibleOnly: true on subscribe, a promise that every push will produce a user-visible notification, so silent background pushes are not a supported use of the API. Firefox also applies a quota to push messages that do not generate a notification.
Browser and Platform Support
Per MDN, the Push API has been Baseline widely available since March 2023, meaning it works across current versions of Chrome, Edge, Firefox and Safari on desktop, and on Chrome and Firefox for Android. Two caveats matter more than the headline.
First, the Notifications API is not uniformly available. MDN flags it as limited availability because the Notification() constructor throws a TypeError on most mobile browsers. For anything that must work on phones, use persistent notifications through ServiceWorkerRegistration.showNotification() instead, which is the service worker path you are on anyway.
Second, notification options are unevenly implemented. Action buttons, badges, images and requireInteraction vary across browsers and operating systems, so design notifications that still read correctly with only a title, a body and an icon.
The iOS and iPadOS requirement
This caveat decides whether web push is viable for a mobile-heavy audience, and it is stated wrongly almost everywhere.
Apple added Web Push in iOS and iPadOS 16.4, and it works only for web apps that have been added to the Home Screen. As WebKit put it, “we are adding support for Web Push to Home Screen web apps”, and “a web app that has been added to the Home Screen can request permission to receive push notifications”. The user adds it through the Share menu and “Add to Home Screen”, and permission must then be requested in response to direct user interaction, such as tapping a subscribe button.
A site open in an ordinary Safari tab on iPhone cannot create a push subscription. That is a real barrier: you are asking for an installation step before you can even ask for permission. On macOS it is easier, because Safari 16.1 on macOS Ventura added standards-based Web Push for regular websites with no installation step.
A Minimal Subscription Example
This is the whole client-side flow. It belongs in a click handler, not on page load.
async function subscribeToPush(vapidPublicKey) { // Push and service workers require a secure context (HTTPS). if (!("serviceWorker" in navigator) || !("PushManager" in window)) return null;
const registration = await navigator.serviceWorker.register("/sw.js");
// Must be called from a user gesture, and only once per user. const permission = await Notification.requestPermission(); if (permission !== "granted") return null;
const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: vapidPublicKey, // base64url-encoded P-256 public key });
// Persist endpoint + keys server-side; treat the endpoint as a secret. await fetch("/api/push/subscribe", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(subscription), });
return subscription;}Inside sw.js you handle the push event, call self.registration.showNotification(title, options), and handle notificationclick to open the destination URL.
Permission UX: Where Most Programmes Fail
Never ask on page load
Lighthouse has a dedicated audit for this: “if your page asks for permission to send notifications on page load, those notifications may not be relevant to your users or their needs”. Its recommendation is to offer a specific type of notification and request permission only after the user opts into that type. Safari 12.1 and other browsers went further, requiring interaction with the page before a request can be made at all.
Use a soft pre-prompt
Show your own in-page invitation first. It names the value proposition, it can be dismissed with no permanent cost, and only a click on it triggers the real browser prompt. Someone who ignores your soft prompt today can be asked again next month. Someone who clicks Block cannot. Two rules make it work: describe what you will actually send rather than “get updates”, and never engineer a mistaken click, because an accidental accept produces an immediate unsubscribe.
Ask in context
Chrome’s own recommendation is to “let your users take the initiative and turn on notifications at their own pace” by placing toggles discreetly within existing interface surfaces, and to avoid “showing prompts and/or overlays without context or immediately after a user lands on the site”.
The moments that work in commerce are specific: a “notify me when this is back in stock” control on a sold-out product, an order confirmation page offering delivery updates, a price-watch toggle on a frequently viewed product. The permission is traded for a named benefit rather than harvested.
A denial is effectively permanent
When someone blocks notifications, the browser stores that decision for your origin. Later calls to Notification.requestPermission() resolve to the stored denied value without showing anything, which is why MDN’s own example checks Notification.permission before ever calling requestPermission(). Reversing a block means digging into site settings, which effectively nobody does.
What browsers do when you get it wrong
The consequences are no longer just a low opt-in rate.
- Chrome automatically enrols origins with very low accept rates into a quieter permission UI, separately by device type, suppressing the prompt for everyone.
- Chrome rate limits sites that combine high push volume with low engagement, returning HTTP 429. Escalation runs one day, then seven, then fourteen, resetting only after 42 consecutive non-disruptive days.
- Chrome now automatically revokes notification permission for sites a user has not interacted with recently, where there is “very low user engagement and a high volume of notifications being sent”. Google’s justification is stark: “Less than 1% of all notifications receive any interaction from users.”
You can lose subscribers you already earned simply by sending badly.
Web Push Compared With Email and SMS
| Factor | Web push | SMS | |
|---|---|---|---|
| Reach | Only browsers that opted in | Anyone with the address | Anyone with the number |
| Marginal cost | Effectively zero | Very low | Per message, the highest |
| Immediacy | Seconds, surfaced by the OS | Minutes to days, buried in an inbox | Seconds |
| Message length | A title and a short body | Unlimited, rich formatting | Roughly 160 characters per segment |
| Consent | Browser prompt, one click | Address collection, ideally double opt-in | Explicit and heavily regulated |
| Identity | A browser on one device | A person | A person |
| Portability | None | Full export | Full export |
The ownership difference that changes strategy
A push subscription is a capability URL bound to one browser profile on one device. It is not a person. The same customer using Chrome on a laptop and Firefox on a phone is two unrelated subscriptions, and you cannot know they are the same human unless they identify themselves.
It is also not portable. You can export an email list and load it into another platform tomorrow. You cannot move push subscriptions between vendors, because the keys and VAPID binding were created against a specific application server key.
So treat web push as an accelerant on an owned channel, never a replacement. Use the push moment to earn an email address or a phone number, not the other way round. The marketing automation complete guide covers wiring several channels into one journey.
Use Cases That Genuinely Work
The channel rewards messages that are time-sensitive, personally relevant and actionable in one tap.
- Cart abandonment. A push within an hour, reinforced by an email later. The abandoned cart email guide covers the sequencing.
- Back-in-stock alerts. The strongest case, because the user explicitly asked to be told.
- Price drops on watched items. Same logic, self-selected relevance.
- Delivery and order status. High open intent, low complaint risk.
- Breaking updates in a subscribed topic. News, results, availability windows.
What fails is equally clear: generic “we published a new post” broadcasts, undifferentiated daily deals, anything needing more than a title and one line, re-engagement blasts to subscribers who ignored the last twenty notifications, and transactional content that needs a durable record.
Frequency, Timing, and Segmentation
Start conservative: one to three notifications per subscriber per week, expanding only if opt-out and click rates hold. Fatigue shows up faster than in email because muting costs one tap on a notification the operating system already surfaced.
Timing is both an advantage and a hazard. Push arrives immediately, so a message sent at 02:00 arrives at 02:00. Store or infer a subscriber’s timezone at subscribe time and hold sends inside a defined window.
Segmentation is constrained by what you know about a subscription rather than a person, so the workable dimensions are behavioural: pages viewed, products watched, cart state, purchase recency, platform. The customer segmentation guide goes deeper.
Measuring Web Push
Four metrics matter, and they are not all measurable the same way.
- Delivery. Whether the push service accepted the request. A 201 means accepted, not delivered. A 404 or 410 means the subscription is dead.
- Display. Whether the notification was shown. You only know this if the service worker reports back when
showNotification()resolves. - Click-through rate. Clicks divided by displays. This is the number worth optimising.
- Opt-out rate. Unsubscribes and permission revocations per send. Watch it more closely than CTR, because it is the leading indicator of channel death.
Attribution pitfalls
Push attribution flatters itself. The notification arrives on a device the subscriber is already holding, so it often takes credit for a session that was going to happen anyway. Run holdout groups rather than assuming incrementality. Displays are undercounted while every click is recorded, so a CTR computed against sends overstates performance. And because a subscription is a browser rather than a person, a push clicked on a phone that ends in a desktop purchase looks like two unrelated events. The email marketing metrics guide covers measurement hygiene across channels.
Consent, GDPR, and Opt-Out
The browser permission prompt is a technical gate. It is not automatically a complete legal basis for marketing.
Where you are marketing to people in the EU or UK, treat push the way you treat email. Explain what you will send before the prompt appears, so the consent is informed and specific. Record when and where the subscription was created, and never bundle push consent into an unrelated action. If you tie subscriptions to identified customers, that data sits inside your personal-data obligations, including deletion requests.
Opt-out hygiene matters just as much. Offer an in-site preferences control so people can reduce frequency instead of blocking, call PushSubscription.unsubscribe() and delete the record server-side when they do, and purge subscriptions on a 404 or 410. MDN’s guidance is short and correct: users should be “offered an easy way to opt out of getting more in the future”.
Where Web Push Fits in a Channel Stack
Web push is a good third channel and a bad first one: fast, free at the margin and unrivalled for time-critical alerts, but device-bound, unexportable and one click away from permanent loss.
That makes orchestration the real problem. Which message goes to which channel, how you suppress the email when the push already converted, and how you keep one view of the customer across surfaces that identify people differently. Brevo offers web and mobile push alongside email and SMS, and Tajo sits on top of Brevo to coordinate that cross-channel logic for Shopify stores. For the SMS side, see the SMS automation guide.
Key Takeaways
- Web push is three APIs cooperating: a service worker for background execution, the Push API for subscription and transport, the Notifications API for display. A subscription is an endpoint plus a
p256dhkey and anauthsecret, payloads are encrypted end to end, and VAPID proves the sender. - Push has been Baseline widely available since March 2023, but on iOS and iPadOS it works only for web apps added to the Home Screen.
- Never request permission on page load. Use a soft pre-prompt, ask in context, and remember that a block is permanent for that origin.
- Chrome now enforces quieter prompts, rate limits and automatic permission revocation, so sending badly costs you subscribers you already have.
- A subscription is a browser, not a person, and cannot be exported. Build the email list first and use push to accelerate it.