How Push API Receives Messages in Service Workers
The Push API enables web applications to receive messages sent from a
remote server even when the web application is not actively open in the
browser. This article explains the underlying mechanism of this process,
covering how subscriptions are established, how messages travel securely
from an application server through a browser push service, and how the
JavaScript service worker intercepts, decrypts, and processes the
incoming push event to display notifications or update
local data.
The Push Architecture Components
The Push API relies on four primary components interacting across the network:
- User Agent (Browser): The client environment managing user permissions, service workers, and active network connections to the push service.
- Service Worker: The background JavaScript worker running independently of web pages, responsible for handling push events.
- Push Service: A browser-vendor-hosted infrastructure (e.g., Mozilla Push Service, Google FCM, Apple Push Notification service) that maintains persistent connections with the browser and queues messages.
- Application Server: Your backend server that generates notifications and triggers push requests.
Step-by-Step Delivery Flow
1. Creating the Subscription
Before a message can be received, the browser registers a service
worker and requests a PushSubscription via
pushManager.subscribe(). During this step: - The browser
contacts its vendor’s push service. - The push service creates a unique
endpoint URL tied to the specific browser instance. - The subscription
object generates cryptographic public keys (P-256 curve and an auth
secret). - The client sends this subscription data (endpoint and public
keys) to your backend application server for storage.
2. Transmitting from Server to Push Service
When your application server needs to send a push message: - It encrypts the payload using the client’s public keys via the RFC 8291 (Message Encryption for Web Push) standard. - It authenticates itself using VAPID (Voluntary Application Server Identification) keys via JSON Web Tokens (JWT). - It sends a standard HTTP POST request containing the encrypted payload directly to the unique endpoint URL provided by the push service.
3. Routing from Push Service to the Browser
The push service receives the HTTP POST request and validates the VAPID headers. It checks if the target device is online: - If the device is offline, the push service queues the message until the device reconnects or the message Time-To-Live (TTL) expires. - If the device is online, the push service routes the encrypted payload through the persistent, low-overhead network connection maintained between the operating system/browser and the push service.
4. Waking the Service Worker
Once the browser receives the message from the push service: 1. The
browser decrypts the payload using its stored private key. 2. The
browser locates the service worker associated with the subscription. 3.
If the service worker is idle or terminated, the browser spins up the
service worker environment in the background. 4. The browser dispatches
a push event to the service worker’s global execution
context.
Handling the Event in the Service Worker
Inside the service worker, a listener handles the incoming
push event. The event.data property provides
access to the decrypted payload in various formats (text, JSON, blob, or
arrayBuffer).
self.addEventListener('push', (event) => {
let data = {};
if (event.data) {
data = event.data.json();
}
const title = data.title || 'New Notification';
const options = {
body: data.body || 'You have received a new update.',
icon: '/images/icon.png',
badge: '/images/badge.png',
data: {
url: data.url || '/'
}
};
// Keep the service worker alive until the notification is displayed
event.waitUntil(
self.registration.showNotification(title, options)
);
});Extending Lifetime with
event.waitUntil()
Because service workers can be terminated by the browser at any time
to conserve memory and battery, the event.waitUntil()
method must be used. It takes a JavaScript Promise and signals to the
browser that work is ongoing. The browser keeps the service worker
running until the Promise resolves—typically when
self.registration.showNotification() finishes displaying
the system notification.