Everything, Everywhere
Verified Specification | Standardized Formulas | Instant Precision
Secure & Private (Zero Data Retention) Free Access • No Sign-Up
RFC 8030 (Web Push) RFC 8292 (VAPID) RFC 8291 (aes128gcm) Web Crypto API Native

Web Push Notifications, VAPID & RFC 8291 Cryptography Studio

An end-to-end cryptographic and protocol architecture studio for Web Push. Generate NIST P-256 keypairs, sign RFC 8292 VAPID authentication JWTs, derive ECDH shared secrets and HKDF encryption keys, assemble RFC 8291 aes128gcm binary records, inspect HTTP/2 push gateway wire frames, and synthesize production Service Workers and backend dispatchers.

4,096 B
RFC 8291 Max Payload
P-256
NIST Elliptic Curve
AES-128-GCM
Payload Content-Encoding
86 Bytes
RFC 8188 Frame Overhead
16 Bytes
AEAD Auth Tag

VAPID NIST P-256 Keypair Generator & JWT Synthesizer

Voluntary Application Server Identification (RFC 8292) allows push services to identify your application server without requiring vendor-specific developer accounts. The server signs a short-lived JSON Web Token (JWT) using its private P-256 key, matching the public key supplied during browser subscription.

Passed to pushManager.subscribe({ applicationServerKey: urlBase64ToUint8Array(...) })
Never expose this key to client-side code or public repositories.

VAPID JWT Claims & Header Synthesizer

Must be mailto: address or HTTPS contact URL.
RFC 8292 recommends max 24h (12h recommended).
Generating VAPID JWT...
Authorization: vapid t=..., k=...
Authorization: WebPush ... Crypto-Key: p256ecdsa=...

RFC 8291 Message Encryption & Binary Record Framing (aes128gcm)

Web Push encrypts arbitrary notification payloads using the user agent's ephemeral ECDH public key (p256dh) and authentication secret (auth). The record is encapsulated in RFC 8188 binary format with an 86-byte header, followed by AES-128-GCM ciphertext and a 16-byte authentication tag.

RFC 8291 Payload Capacity Budget 0 / 4,096 Bytes

RFC 8188 / RFC 8291 Binary Record Frame Anatomy

Salt [16B]
rs [4B]
id [1B]
Ephemeral AS Public Key [65B]
Ciphertext + Delimiter
Auth Tag [16B]
Byte Range Field Name Length Semantic Value & Purpose
00 .. 15 salt 16 Bytes Cryptographically random salt for HKDF PRK derivation
16 .. 19 rs (Record Size) 4 Bytes 0x00 0x00 0x10 0x00 (4,096 Octets in Network Byte Order)
20 idlen (Key ID Length) 1 Byte 0x41 (65 Octets for uncompressed P-256 public key)
21 .. 85 keyid (AS Public Key) 65 Bytes Ephemeral server public key (0x04 || X || Y)
86 .. N-16 Ciphertext Variable AES-128-GCM encrypted payload ending with delimiter 0x02
N-16 .. N tag (AEAD Tag) 16 Bytes 128-bit authentication tag validating payload integrity
Click 'Encrypt Notification Payload' to execute live Web Crypto AES-128-GCM...

Push Gateway HTTP/2 Dispatch & Wire Inspector

Application servers dispatch encrypted push notifications by issuing an HTTP/2 POST request directly to the subscription endpoint with standard RFC 8030 headers: TTL, Urgency, and Topic.

0 = deliver immediately or discard; max 2,419,200 (28 days).
Replaces queued notifications with matching topic.
POST /fcm/send/sample HTTP/2 Host: fcm.googleapis.com ...
curl -v -X POST ...

Push Service HTTP Response Diagnostics

Status Code Meaning Required Backend Action
201 Created Accepted for Delivery Message queued or transmitted to client device. Store Location header for delivery status polling if supported.
400 Bad Request Invalid Payload / Header Malformed RFC 8188 record, invalid Base64URL encoding, or invalid header value. Inspect payload size and crypto headers.
401 Unauthorized VAPID Authentication Failure VAPID token expired, signature verification failed, or aud origin does not match the push gateway. Regenerate JWT.
404 / 410 Gone Subscription Dead / Expired MANDATORY: Immediately delete the subscription from your database. The user revoked permission or uninstalled the browser.
413 Payload Too Large Exceeded 4,096 Octets Payload exceeds push service capacity. Strip embedded images or move rich metadata to an API fetch inside the Service Worker.
429 Too Many Requests Rate Limited / Quota Exceeded Respect the Retry-After header and throttle outgoing worker concurrency with exponential backoff.

Client-Side Registration & Service Worker Implementation

Web Push client lifecycle involves requesting user permission, converting the VAPID public key to a Uint8Array, subscribing via PushManager, and registering resilient push and notificationclick handlers inside the Service Worker.

// 1. Helper to decode VAPID Base64URL string to Uint8Array function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding) .replace(/-/g, '+') .replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } // 2. Register Service Worker and subscribe to Push async function subscribeUserToPush(vapidPublicKey) { if (!('serviceWorker' in navigator) || !('PushManager' in window)) { throw new Error('Push notifications are not supported on this browser.'); } // Request user permission const permission = await Notification.requestPermission(); if (permission !== 'granted') { throw new Error('Notification permission denied by user.'); } const registration = await navigator.serviceWorker.register('/sw.js'); await navigator.serviceWorker.ready; // Subscribe with VAPID applicationServerKey const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, // Mandatory for Chrome and Safari applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) }); // Transmit PushSubscription to backend database await fetch('/api/push/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(subscription) }); console.log('Successfully registered Web Push subscription:', subscription); return subscription; }
// sw.js — Production Web Push Event & Notification Click Handler self.addEventListener('push', function(event) { if (!event.data) { console.warn('Push event received with empty payload.'); return; } let payload; try { payload = event.data.json(); } catch (err) { payload = { title: 'Notification', body: event.data.text() }; } const options = { body: payload.body || 'New alert received.', icon: payload.icon || '/assets/icon-192.png', badge: payload.badge || '/assets/badge-72.png', tag: payload.tag || 'general-alert', renotify: payload.renotify !== undefined ? payload.renotify : true, vibrate: payload.vibrate || [100, 50, 100], data: { url: payload.url || '/', timestamp: Date.now(), customData: payload.data || {} }, actions: payload.actions || [ { action: 'open', title: 'Open' }, { action: 'dismiss', title: 'Dismiss' } ] }; // Critical: event.waitUntil prevents browser from terminating the worker event.waitUntil( self.registration.showNotification(payload.title || 'System Notification', options) ); }); // Handle user clicking the notification self.addEventListener('notificationclick', function(event) { event.notification.close(); if (event.action === 'dismiss') { return; } const targetUrl = event.notification.data && event.notification.data.url ? event.notification.data.url : '/'; event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function(clientList) { // Focus existing tab if open for (let i = 0; i < clientList.length; i++) { const client = clientList[i]; if (client.url === targetUrl && 'focus' in client) { return client.focus(); } } // Otherwise open new browser window if (clients.openWindow) { return clients.openWindow(targetUrl); } }) ); }); // Handle subscription invalidation / rotation self.addEventListener('pushsubscriptionchange', function(event) { event.waitUntil( self.registration.pushManager.subscribe(event.oldSubscription.options).then(function(newSub) { return fetch('/api/push/resubscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ oldEndpoint: event.oldSubscription.endpoint, newSubscription: newSub }) }); }) ); });
iOS / iPadOS 16.4+ Web Push Guardrails: Safari on iOS only supports Web Push when the website is installed to the Home Screen as a standalone PWA. Web Push cannot be enabled from inside the regular Safari browser tab. Ensure your site includes a valid manifest.json with "display": "standalone".

Production Backend Dispatch Blueprints

Enterprise implementation templates featuring automated VAPID token signing, RFC 8291 encryption, TTL/urgency tuning, and automatic 410 Gone dead-subscription pruning.

Web Push Architecture & Production Failure Traps

Deep-dive into the security guarantees of the Web Push protocol and the top 6 operational traps that take down production push notification systems.

1. The Unpadded Base64URL Decoding Trap (DOMException) atob() throws DOMException: The string to be decoded is not correctly encoded if the VAPID public key lacks padding = characters or contains standard Base64 +// characters instead of URL-safe -/_. Always use the canonical urlBase64ToUint8Array() helper with '='.repeat((4 - (str.length % 4)) % 4) padding normalization.
2. The 410 Gone Retrial Poisoning Trap When a user unregisters notifications or uninstalls the browser, the push service returns HTTP 410 Gone. If your backend worker treats 410 as a retryable transient failure and continues pushing, push services (especially Google FCM and Apple APNs) will flag your VAPID sender IP and apply severe global rate limiting. Every 410 Gone and 404 Not Found must trigger an immediate DB delete.
3. The Missing event.waitUntil() Process Termination Trap Service Workers in mobile browsers are aggressively paused or terminated to save memory and battery. If you call registration.showNotification() without wrapping the promise inside event.waitUntil(), the mobile browser will terminate the Service Worker thread before the notification IPC completes, causing notifications to disappear silently.
4. The VAPID Audience (aud) Origin Mismatch Trap (401 Unauthorized) RFC 8292 Section 2 mandates that the aud claim in the VAPID JWT must equal the push service origin (e.g. https://fcm.googleapis.com for FCM, https://web.push.apple.com for Apple, https://updates.push.services.mozilla.com for Firefox). If your backend hardcodes https://fcm.googleapis.com for all endpoints, pushes to Apple or Mozilla users will immediately fail with 401 Unauthorized. You must parse the origin dynamically from new URL(subscription.endpoint).origin.
5. The 4,096-Byte Payload Budget Overflow Trap (413 Payload Too Large) RFC 8291 limits the total encrypted record body to 4,096 bytes. Remember that the binary frame consumes 86 bytes of header overhead plus 16 bytes of GCM authentication tag (102 bytes total). If your plaintext JSON is larger than 3,993 bytes (e.g. by embedding Base64 image data), the push service returns 413 Payload Too Large. Never embed images in payload; send an image URL instead.
6. The Silent Push Enforcement Failure on iOS Safari Web standards permit userVisibleOnly: false for silent background data sync pushes. However, WebKit and Chrome strictly enforce userVisibleOnly: true. If an application attempts to call showNotification() conditionally or attempts a silent push on iOS, WebKit revokes the push subscription entirely to prevent background battery drains and stealth tracking.

Frequently Asked Technical Questions

Why does the Web Push protocol (RFC 8030 / RFC 8291) require end-to-end encryption instead of relying on TLS?+
In the Web Push architecture, the push service (e.g. Google FCM, Apple Push Notification service, or Mozilla Autopush) acts as an untrusted intermediate broker between the application server and the user agent. While TLS secures the hop between the application server and the push service, and between the push service and the device, TLS alone would allow the push service provider to inspect, store, or modify all push notification payloads. RFC 8291 mandates end-to-end message encryption using ECDH on NIST P-256, HKDF SHA-256, and AES-128-GCM. Because the cryptographic keys (p256dh public key and auth secret) are generated inside the user agent and never shared with the push gateway, the intermediary can only see encrypted binary octets, guaranteeing zero plaintext exposure to Google, Apple, or intermediate network operators.
How does a mobile or desktop OS maintain an active push notification connection without draining battery?+
Rather than every website maintaining an independent persistent WebSocket or HTTP polling connection, the operating system maintains a single multiplexed, low-power TCP connection with a system-level push gateway (APNs for Apple devices, Google Play Services / FCM for Android, WNS for Windows). This socket utilizes long keep-alive intervals (often 15 to 30 minutes) and cellular network hardware interrupts (such as radio wake-up paging channels). When an application server posts an encrypted push record to the push gateway endpoint, the gateway routes it down this single persistent channel. The OS wakes only the targeted browser process and passes the encrypted payload to the registered Service Worker via an IPC push event, preventing background CPU spinning.
What is the critical difference between RFC 8291 (aes128gcm) and the legacy draft-ietf-webpush-encryption (aesgcm)?+
The initial draft of Web Push encryption used Content-Encoding: aesgcm with headers Crypto-Key and Encryption specifying the salt and server public key. This was found to be brittle and vulnerable to header tampering and stripping by intermediate proxies. In 2017, the IETF ratified RFC 8188 (Encrypted Content-Encoding for HTTP) and RFC 8291, replacing aesgcm with aes128gcm. Under RFC 8291, the 86-byte encryption metadata (16-byte salt, 4-byte record size, 1-byte key ID length, and 65-byte uncompressed server public key) is encapsulated directly into the binary body header rather than HTTP headers. All modern browsers (Chrome 60+, Firefox 55+, Safari 16.4+, Edge 79+) use aes128gcm exclusively.
Why does Safari on iOS and iPadOS strictly require a Progressive Web App (PWA) to be added to the Home Screen for Web Push?+
Apple introduced Web Push support in iOS and iPadOS 16.4 with strict platform guardrails. Unlike desktop Safari or Android Chrome where any HTTPS site can request push permission directly in-browser, iOS and iPadOS require the user to explicitly install the website as a PWA via "Add to Home Screen" (manifest.json with display: standalone). Furthermore, the Notification.requestPermission() prompt must be triggered by a direct user gesture (such as tapping a button inside the PWA). Apple enforced this to prevent mobile web spam, ensure push notifications match native app expectations, and conserve battery by restricting background Service Worker wakeups to user-curated apps.
How should a high-throughput backend handle HTTP 410 Gone and 404 Not Found responses from push services?+
An HTTP 410 Gone (or 404 Not Found) response from a push gateway indicates that the user has revoked notification permissions, uninstalled the browser, or the subscription has reached its lifecycle expiration. Continuing to dispatch push messages to expired endpoints wastes server CPU and bandwidth and degrades server reputation with push gateways (which will eventually throttle or rate-limit the application server with 429 Too Many Requests). Production dispatch workers must immediately mark the subscription as dead in the database and prune it from future broadcast batches. Similarly, HTTP 429 responses must be respected using the Retry-After header with exponential backoff.
How does the Web Push Topic header enable notification collapsing and prevent alert storms?+
The Topic header (RFC 8030 Section 5.4) allows the application server to assign an alphanumeric tag (up to 32 characters) to a push message, acting as a collapse key. If a user device is currently offline or sleeping, and the application server dispatches multiple notifications with the same Topic (for example, Topic: sport-scores-live or Topic: chat-room-104), the push service retains only the most recently received message. When the device reconnects, the user receives a single updated alert rather than being flooded with dozens of stale, obsolete notifications. If Topic is omitted, every push message is queued independently until the TTL expires.
Sponsored Utility
While You're Here
Sponsored Recommendations
Advertisement