Learn how to easily add push notifications to your web app and boost user engagement with our step-by-step guide.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
The Business Value of Push Notifications
Before diving into the technical implementation, let's talk about why you might want to invest in push notifications. When implemented thoughtfully, they can:
Web Push Notifications in Plain English
Web push notifications allow your website to send notifications to users even when they're not actively browsing your site. Think of them as text messages from your website to your user's device - they can appear on desktops, mobile phones, and tablets.
Step 1: Check Browser Compatibility
Push notifications are supported by most modern browsers, but there are differences in implementation. Chrome, Firefox, Edge, and Opera have good support. Safari requires a different approach using Apple's Push Notification Service.
// Simple function to check if push notifications are supported
function arePushNotificationsSupported() {
return 'serviceWorker' in navigator && 'PushManager' in window;
}
// Usage
if (arePushNotificationsSupported()) {
// Enable push notification functionality
} else {
// Provide fallback or inform the user
console.log('Push notifications not supported in this browser');
}
Step 2: Set Up a Service Worker
Service workers are JavaScript files that run in the background, separate from your web page. They're essential for push notifications as they can receive and display notifications even when the user isn't on your site.
First, create a file named service-worker.js in your project's root directory:
// service-worker.js
self.addEventListener('push', function(event) {
// Extract notification data from the push event
const data = event.data.json();
// Display the notification
const promiseChain = self.registration.showNotification(data.title, {
body: data.body,
icon: data.icon,
badge: data.badge,
data: data.data
});
event.waitUntil(promiseChain);
});
// Handle notification clicks
self.addEventListener('notificationclick', function(event) {
// Close the notification
event.notification.close();
// This looks to see if the current is already open and focuses it
event.waitUntil(
clients.matchAll({
type: "window"
})
.then(function(clientList) {
// If we have a url to navigate to (from the notification data)
if (event.notification.data && event.notification.data.url) {
const url = event.notification.data.url;
// Try to find an existing window and navigate it
for (let i = 0; i < clientList.length; i++) {
const client = clientList[i];
if (client.url === url && 'focus' in client)
return client.focus();
}
// If no existing window, open a new one
if (clients.openWindow)
return clients.openWindow(url);
}
})
);
});
Next, register this service worker in your main JavaScript file:
// Register the service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker registered with scope:', registration.scope);
})
.catch(function(error) {
console.error('Service Worker registration failed:', error);
});
}
Step 3: Request User Permission
Before sending notifications, you need explicit permission from the user. This is a critical moment in your user experience - request permission at the right time, when the user understands the value they'll receive.
// Request permission at the appropriate time in your user journey
function requestNotificationPermission() {
return new Promise(function(resolve, reject) {
const permissionResult = Notification.requestPermission(function(result) {
// Handle callback-based API (older browsers)
resolve(result);
});
// Handle Promise-based API (newer browsers)
if (permissionResult) {
permissionResult.then(resolve, reject);
}
})
.then(function(permissionResult) {
if (permissionResult !== 'granted') {
throw new Error('Permission not granted.');
}
console.log('Notification permission granted!');
return true;
});
}
// Example of good UX: Request permission after user action
document.getElementById('enable-notifications').addEventListener('click', function() {
requestNotificationPermission()
.then(subscribeUserToPush)
.catch(function(error) {
console.error('Failed to enable notifications:', error);
});
});
Step 4: Subscribe the User to Push Notifications
Once you have permission, you need to subscribe the user to your push service. This involves generating a public/private key pair.
First, generate your VAPID keys. You can use the web-push library if you're using Node.js:
// Install web-push library
npm install web-push -g
// Generate VAPID keys
web-push generate-vapid-keys --json > vapid-keys.json
Then, implement the subscription logic:
// The publicKey should be your VAPID public key
const publicKey = 'YOUR_PUBLIC_VAPID_KEY';
function subscribeUserToPush() {
return navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicKey)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function(pushSubscription) {
console.log('Received PushSubscription:', JSON.stringify(pushSubscription));
// Send the subscription to your server
return sendSubscriptionToServer(pushSubscription);
});
}
// Helper function to convert base64 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;
}
// Send the subscription to your server
function sendSubscriptionToServer(subscription) {
return fetch('/api/save-subscription', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(subscription)
})
.then(function(response) {
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function(responseData) {
if (!(responseData && responseData.success)) {
throw new Error('Bad response from server.');
}
return responseData;
});
}
Step 5: Store Subscriptions on Your Server
Your server needs to store each user's subscription information. Here's a simple Node.js/Express implementation:
// Server-side code (Node.js/Express)
const express = require('express');
const webpush = require('web-push');
const bodyParser = require('body-parser');
const app = express();
// Set VAPID details for web push
const vapidKeys = {
publicKey: 'YOUR_PUBLIC_VAPID_KEY',
privateKey: 'YOUR_PRIVATE_VAPID_KEY'
};
webpush.setVapidDetails(
'mailto:[email protected]',
vapidKeys.publicKey,
vapidKeys.privateKey
);
app.use(bodyParser.json());
// In-memory store for subscriptions (use a database in production)
const subscriptions = [];
// Save subscription endpoint
app.post('/api/save-subscription', function(req, res) {
const subscription = req.body;
// Store the subscription
subscriptions.push(subscription);
res.json({ success: true });
});
// Start the server
app.listen(3000, function() {
console.log('Server started on port 3000');
});
Step 6: Send Push Notifications
Now you can send notifications to your users! Here's how to send a notification to all subscribers:
// Server-side code to send notifications
app.post('/api/trigger-push-notification', function(req, res) {
const notificationPayload = {
title: 'New Notification',
body: 'This is the body of the notification',
icon: '/path/to/icon.png',
badge: '/path/to/badge.png',
data: {
url: 'https://example.com/path-to-open-on-click'
}
};
// Send notifications to all subscribers
const sendPromises = subscriptions.map(subscription => {
return webpush.sendNotification(
subscription,
JSON.stringify(notificationPayload)
)
.catch(err => {
if (err.statusCode === 410) {
// Subscription has expired or is no longer valid
return removeSubscription(subscription);
} else {
console.log('Subscription is no longer valid: ', err);
}
});
});
Promise.all(sendPromises)
.then(() => res.json({ success: true }))
.catch(err => {
console.error("Error sending notifications:", err);
res.status(500).json({ success: false });
});
});
// Helper function to remove invalid subscriptions
function removeSubscription(subscription) {
const index = subscriptions.findIndex(item =>
item.endpoint === subscription.endpoint
);
if (index !== -1) {
subscriptions.splice(index, 1);
}
}
1. Segmentation and Personalization
Don't send the same notification to all users. Store additional data with each subscription to enable targeting:
// Store user preferences with subscription
app.post('/api/save-subscription', function(req, res) {
const subscription = req.body.subscription;
const preferences = req.body.preferences || {};
const userId = req.user.id; // Assuming authentication
// Store in database with user context
db.subscriptions.create({
userId: userId,
subscription: subscription,
preferences: preferences
});
res.json({ success: true });
});
// Send targeted notifications
function sendNotificationToSegment(segment, payload) {
// Get subscriptions that match segment criteria
return db.subscriptions.findAll({
where: {
preferences: {
[segment.key]: segment.value
}
}
})
.then(subscriptions => {
// Send notifications to this segment
return Promise.all(
subscriptions.map(sub =>
webpush.sendNotification(sub.subscription, JSON.stringify(payload))
)
);
});
}
2. Analytics and Tracking
Track notification performance to improve your strategy:
// In your service worker
self.addEventListener('push', function(event) {
const data = event.data.json();
// Track that notification was received
fetch('/api/track-notification', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: data.id,
event: 'received'
})
});
// Display the notification
const promiseChain = self.registration.showNotification(data.title, {
body: data.body,
icon: data.icon,
data: { id: data.id, url: data.url }
});
event.waitUntil(promiseChain);
});
self.addEventListener('notificationclick', function(event) {
// Track that notification was clicked
fetch('/api/track-notification', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: event.notification.data.id,
event: 'clicked'
})
});
// Handle the click...
});
3. Multi-Platform Support
For Safari on macOS, you'll need to use Apple's Push Notification Service:
// Check if using Safari on macOS
function isSafariMacOS() {
const userAgent = window.navigator.userAgent;
return userAgent.includes('Safari') &&
!userAgent.includes('Chrome') &&
userAgent.includes('Macintosh');
}
// Conditional registration based on browser
if (isSafariMacOS()) {
// Use Safari-specific push notification API
// This requires a paid Apple Developer account
const safariPushService = window.safari.pushNotification;
// Check permission
let permissionData = safariPushService.permission('web.com.example');
if (permissionData.permission === 'default') {
// Request permission
safariPushService.requestPermission(
'https://example.com/push-service', // Your push service URL
'web.com.example', // Your website push ID
{}, // User data for your server to store
function(permissionData) {
if (permissionData.permission === 'granted') {
console.log('Safari push token:', permissionData.deviceToken);
// Send this token to your server
}
}
);
}
} else if (arePushNotificationsSupported()) {
// Use standard Web Push API as shown earlier
} else {
// Fallback for unsupported browsers
}
Technical Pitfalls to Avoid
User Experience Best Practices
Here's how you might implement a complete notification system for an e-commerce application:
// Main application JS
document.addEventListener('DOMContentLoaded', function() {
// Initialize push notifications when document is ready
initializePushNotifications();
// Add event listener to notification opt-in button
document.getElementById('enable-notifications').addEventListener('click', function() {
subscribeToPushNotifications();
});
});
// Initialize push notifications
function initializePushNotifications() {
// Check if push notifications are supported
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
console.log('Push notifications not supported');
document.getElementById('notification-status').textContent = 'Push notifications not supported in this browser';
return;
}
// Register service worker
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker registered with scope:', registration.scope);
// Check notification permission status
return Notification.requestPermission().then(function(permission) {
if (permission === 'granted') {
document.getElementById('notification-status').textContent = 'Notifications enabled';
document.getElementById('enable-notifications').style.display = 'none';
// Check if already subscribed
return registration.pushManager.getSubscription().then(function(subscription) {
if (subscription) {
return subscription;
}
// If not subscribed, show the subscribe button
document.getElementById('enable-notifications').style.display = 'block';
return null;
});
} else {
document.getElementById('notification-status').textContent = 'Notifications disabled';
document.getElementById('enable-notifications').style.display = 'block';
}
});
})
.catch(function(error) {
console.error('Service Worker registration failed:', error);
});
}
// Subscribe to push notifications
function subscribeToPushNotifications() {
// Request permission first
Notification.requestPermission().then(function(permission) {
if (permission === 'granted') {
// Get service worker registration
navigator.serviceWorker.ready.then(function(registration) {
// Get user preferences
const preferences = {
orderUpdates: document.getElementById('order-updates').checked,
promotions: document.getElementById('promotions').checked,
productRestocks: document.getElementById('product-restocks').checked
};
// Public VAPID key
const publicKey = 'YOUR_PUBLIC_VAPID_KEY';
// Subscribe
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicKey)
})
.then(function(subscription) {
// Send subscription to server along with preferences
return fetch('/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
subscription: subscription,
preferences: preferences
})
});
})
.then(function(response) {
if (!response.ok) {
throw new Error('Failed to store subscription on server');
}
document.getElementById('notification-status').textContent = 'Notifications enabled';
document.getElementById('enable-notifications').style.display = 'none';
document.getElementById('notification-preferences').style.display = 'block';
console.log('Successfully subscribed to push notifications');
})
.catch(function(error) {
console.error('Failed to subscribe to push notifications:', error);
document.getElementById('notification-status').textContent = 'Failed to enable notifications';
});
});
}
});
}
React Integration Example
// PushNotificationComponent.jsx
import React, { useState, useEffect } from 'react';
const PushNotificationComponent = () => {
const [isSupported, setIsSupported] = useState(false);
const [isSubscribed, setIsSubscribed] = useState(false);
const [registration, setRegistration] = useState(null);
useEffect(() => {
// Check if push notifications are supported
if ('serviceWorker' in navigator && 'PushManager' in window) {
setIsSupported(true);
// Register service worker
navigator.serviceWorker.register('/service-worker.js')
.then(reg => {
setRegistration(reg);
// Check if already subscribed
return reg.pushManager.getSubscription();
})
.then(subscription => {
setIsSubscribed(!!subscription);
})
.catch(error => {
console.error('Service Worker registration failed:', error);
});
}
}, []);
const handleSubscribe = async () => {
try {
const permission = await Notification.requestPermission();
if (permission === 'granted' && registration) {
const publicKey = 'YOUR_PUBLIC_VAPID_KEY';
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicKey)
});
// Send subscription to server
const response = await fetch('/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ subscription })
});
if (response.ok) {
setIsSubscribed(true);
}
}
} catch (error) {
console.error('Failed to subscribe:', error);
}
};
if (!isSupported) {
return <p>Push notifications are not supported in this browser.</p>;
}
return (
<div>
{!isSubscribed ? (
<button onClick={handleSubscribe}>
Enable Push Notifications
</button>
) : (
<p>You are subscribed to push notifications!</p>
)}
</div>
);
};
export default PushNotificationComponent;
Vue.js Integration Example
// PushNotification.vue
<template>
<div>
<div v-if="!isSupported">
Push notifications are not supported in this browser.
</div>
<div v-else>
<button v-if="!isSubscribed" @click="subscribe">
Enable Push Notifications
</button>
<p v-else>You are subscribed to push notifications!</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isSupported: false,
isSubscribed: false,
registration: null
};
},
mounted() {
this.initialize();
},
methods: {
async initialize() {
// Check if push notifications are supported
if ('serviceWorker' in navigator && 'PushManager' in window) {
this.isSupported = true;
try {
// Register service worker
const reg = await navigator.serviceWorker.register('/service-worker.js');
this.registration = reg;
// Check if already subscribed
const subscription = await reg.pushManager.getSubscription();
this.isSubscribed = !!subscription;
} catch (error) {
console.error('Service Worker registration failed:', error);
}
}
},
async subscribe() {
try {
const permission = await Notification.requestPermission();
if (permission === 'granted' && this.registration) {
const publicKey = 'YOUR_PUBLIC_VAPID_KEY';
const subscription = await this.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: this.urlBase64ToUint8Array(publicKey)
});
// Send subscription to server
const response = await fetch('/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ subscription })
});
if (response.ok) {
this.isSubscribed = true;
}
}
} catch (error) {
console.error('Failed to subscribe:', error);
}
},
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;
}
}
};
</script>
Push notifications are more than just a technical feature—they're a direct communication channel with your users. When implemented with thoughtfulness and respect for user preferences, they can significantly boost engagement and retention.
Remember that the technical implementation is just the beginning. Your notification strategy should evolve based on:
The most successful push notification implementations are those that provide genuine value to users while respecting their attention. With the technical foundation in place, you can now focus on crafting notification content that truly resonates with your audience.
Explore the top 3 push notification use cases to boost engagement and user retention in your web app.
Keeping users informed about time-sensitive developments that directly impact their experience with your product. These notifications drive immediate action when something requires attention or presents a limited-time opportunity.
Bringing dormant users back into your application through personalized, value-driven prompts. These notifications strategically target users showing signs of decreasing engagement before they churn completely.
Providing real-time status updates about user-initiated processes or system changes affecting their account. These notifications create trust through transparency about what's happening behind the scenes.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â