/web-app-features

How to Add Push Notifications to Your Web App

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

Book a free  consultation
4.9
Clutch rating 🌟
600+
Happy partners
17+
Countries served
190+
Team members
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

How to Add Push Notifications to Your Web App

How to Add Push Notifications to Your Web App

 

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:

  • Increase user engagement by up to 88% according to recent studies
  • Boost retention rates by creating timely touchpoints with users
  • Drive conversions for time-sensitive offers
  • Provide actual value through timely, relevant updates (not just marketing noise)

 

The Technical Foundation: Web Push Notifications

 

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.

 

Implementation Roadmap

 

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);
  }
}

 

Advanced Implementation Considerations

 

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
}

 

Common Pitfalls and Best Practices

 

Technical Pitfalls to Avoid

 

  • Service Worker Scope Issues: Ensure your service worker is placed at the root of your domain to have access to all pages.
  • Permission Fatigue: Don't request permission immediately when a user lands on your site. Wait for user engagement.
  • Over-notification: Sending too many notifications is the fastest way to get users to block you.
  • Poor Error Handling: Implement proper error handling for failed notifications.
  • Insecure Origins: Push notifications require HTTPS, except on localhost for development.

 

User Experience Best Practices

 

  • Provide Clear Value: Explain the benefit of enabling notifications before requesting permission.
  • Allow Easy Opt-Out: Make it simple for users to manage or disable notifications.
  • Respect Time Zones: Don't send notifications at 3 AM unless they're truly urgent.
  • Keep Payloads Small: Push notification payloads should be under 4KB.
  • Test Across Devices: Notifications appear differently across operating systems and browsers.

 

Real-World Implementation Example

 

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';
        });
      });
    }
  });
}

 

Integration with Popular Frontend Frameworks

 

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>

 

Conclusion: Push Notifications as a Strategic Tool

 

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:

  • User behavior analytics
  • A/B testing of different message types
  • Timing optimization
  • Personalization based on user segments

 

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.

Ship Push Notifications 10x Faster with RapidDev

Connect with our team to unlock the full potential of code solutions with a no-commitment consultation!

Book a Free Consultation

Top 3 Push Notifications Usecases

Explore the top 3 push notification use cases to boost engagement and user retention in your web app.

 

Timely Updates & Alerts

 

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.

 

  • Business value: Increases engagement with time-sensitive content, improves conversion rates for limited-time offers, and reduces missed opportunities for both users and your business.
  • Implementation consideration: Establish clear urgency thresholds to avoid notification fatigue. A well-timed alert feels like a service; too many feel like spam.

 

Re-engagement Triggers

 

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.

 

  • Business value: Significantly reduces customer churn, improves lifetime value metrics, and creates opportunities to reactivate revenue streams from existing user bases.
  • Implementation consideration: Base these on behavioral analytics and user segmentation. The most effective re-engagement notifications reference specific user interests or previous actions.

 

Transactional Confirmations

 

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.

 

  • Business value: Reduces support inquiries about transaction status, builds user confidence in your platform's reliability, and creates touchpoints for positive brand reinforcement.
  • Implementation consideration: Design these to be concise yet complete. Users should never have to open the app to understand exactly what happened.


Recognized by the best

Trusted by 600+ businesses globally

From startups to enterprises and everything in between, see for yourself our incredible impact.

RapidDev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with.

They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

Arkady
CPO, Praction
Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost.

He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Donald Muir
Co-Founder, Arc
RapidDev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space.

They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Mat Westergreen-Thorne
Co-CEO, Grantify
RapidDev is an excellent developer for custom-code solutions.

We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Emmanuel Brown
Co-Founder, Church Real Estate Marketplace
Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 

This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Samantha Fekete
Production Manager, Media Production Company
The pSEO strategy executed by RapidDev is clearly driving meaningful results.

Working with RapidDev has delivered measurable, year-over-year growth. Comparing the same period, clicks increased by 129%, impressions grew by 196%, and average position improved by 14.6%. Most importantly, qualified contact form submissions rose 350%, excluding spam.

Appreciation as well to Matt Graham for championing the collaboration!

Michael W. Hammond
Principal Owner, OCD Tech

We put the rapid in RapidDev

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.Â