/web-app-features

How to Add Geofencing Notifications to Your Web App

Learn how to add geofencing notifications to your web app for targeted, location-based user engagement and real-time alerts.

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 Geofencing Notifications to Your Web App

Adding Geofencing Notifications to Your Web App

 

What Is Geofencing and Why Should You Care?

 

Geofencing creates virtual boundaries around physical locations that trigger actions when users enter or exit them. For web apps, this can power location-based notifications that engage users at exactly the right moment and place.

 

Think of a coffee shop app notifying loyal customers of a special when they're within a block, or a retail website reminding someone about items in their cart when they're near a physical store. These context-aware notifications can drive engagement rates 3-5x higher than standard push notifications.

 

The Technical Building Blocks

 

Core Technologies You'll Need

 

  • Geolocation API - The browser's built-in capability to determine a user's location
  • Service Workers - JavaScript that runs in the background, even when your app isn't open
  • Push Notifications API - To deliver notifications to users
  • Background Sync - For reliable delivery even with intermittent connections

 

Implementation Guide: The 5-Step Process

 

Step 1: Setting Up Geolocation Tracking

 

First, you need to request location permissions and track user positions. The browser's Geolocation API handles this:

 

// Request permission to use location
function requestLocationPermission() {
  if (!("geolocation" in navigator)) {
    console.error("Geolocation not supported by this browser");
    return Promise.reject("Geolocation not supported");
  }
  
  return new Promise((resolve, reject) => {
    navigator.permissions.query({ name: 'geolocation' }).then(permissionStatus => {
      if (permissionStatus.state === 'granted') {
        resolve();
      } else if (permissionStatus.state === 'prompt') {
        // We'll need to ask the user
        navigator.geolocation.getCurrentPosition(
          () => resolve(),
          () => reject("Permission denied"),
          { enableHighAccuracy: true }
        );
      } else {
        reject("Permission denied");
      }
    });
  });
}

 

Step 2: Defining Your Geofences

 

Next, define the geographic boundaries for your geofences. Store these as coordinates with a radius:

 

const geofences = [
  {
    id: "store-downtown",
    name: "Downtown Store",
    lat: 40.7128,
    lng: -74.0060,
    radius: 500, // meters
    message: "You're near our downtown location! Stop by for 15% off today.",
    action: "/offers/downtown"
  },
  {
    id: "headquarters",
    name: "Company HQ",
    lat: 37.7749,
    lng: -122.4194,
    radius: 200, // meters
    message: "Welcome to our headquarters! Check in at reception for a visitor badge.",
    action: "/visitor-info"
  }
];

// Helper function to calculate distance between points
function calculateDistance(lat1, lon1, lat2, lon2) {
  // Implementation of the Haversine formula for calculating
  // distance between two points on the Earth's surface
  const R = 6371e3; // Earth's radius in meters
  const φ1 = lat1 * Math.PI/180;
  const φ2 = lat2 * Math.PI/180;
  const Δφ = (lat2-lat1) * Math.PI/180;
  const Δλ = (lon2-lon1) * Math.PI/180;

  const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
          Math.cos(φ1) * Math.cos(φ2) *
          Math.sin(Δλ/2) * Math.sin(Δλ/2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

  return R * c; // distance in meters
}

 

Step 3: Implementing Position Monitoring

 

Now, create a system that monitors position and checks for geofence triggers:

 

class GeofenceMonitor {
  constructor(geofences) {
    this.geofences = geofences;
    this.watchId = null;
    this.enteredGeofences = new Set(); // Track which geofences user is already in
  }

  start() {
    if (this.watchId !== null) return;
    
    const options = {
      enableHighAccuracy: true,
      maximumAge: 30000,        // Accept positions up to 30 seconds old
      timeout: 27000            // Wait up to 27 seconds for a position
    };
    
    this.watchId = navigator.geolocation.watchPosition(
      this.handlePositionUpdate.bind(this),
      this.handleError.bind(this),
      options
    );
    
    console.log("Geofence monitoring started");
  }
  
  stop() {
    if (this.watchId === null) return;
    
    navigator.geolocation.clearWatch(this.watchId);
    this.watchId = null;
    this.enteredGeofences.clear();
    
    console.log("Geofence monitoring stopped");
  }
  
  handlePositionUpdate(position) {
    const currentLat = position.coords.latitude;
    const currentLng = position.coords.longitude;
    
    // Check each geofence
    this.geofences.forEach(geofence => {
      const distance = calculateDistance(
        currentLat, currentLng, 
        geofence.lat, geofence.lng
      );
      
      const isInGeofence = distance <= geofence.radius;
      const wasInGeofence = this.enteredGeofences.has(geofence.id);
      
      // Entered geofence
      if (isInGeofence && !wasInGeofence) {
        this.enteredGeofences.add(geofence.id);
        this.triggerGeofenceEnter(geofence);
      } 
      // Exited geofence
      else if (!isInGeofence && wasInGeofence) {
        this.enteredGeofences.delete(geofence.id);
        this.triggerGeofenceExit(geofence);
      }
    });
  }
  
  handleError(error) {
    console.error("Geolocation error:", error.message);
    // Implement error handling strategy (retry, fallback, etc.)
  }
  
  triggerGeofenceEnter(geofence) {
    console.log(`Entered geofence: ${geofence.name}`);
    // This is where we'll send the notification
    this.sendNotification(geofence);
  }
  
  triggerGeofenceExit(geofence) {
    console.log(`Exited geofence: ${geofence.name}`);
    // Optional: do something when user leaves area
  }
  
  sendNotification(geofence) {
    // We'll implement this in the next step
  }
}

// Usage
const monitor = new GeofenceMonitor(geofences);
requestLocationPermission()
  .then(() => monitor.start())
  .catch(error => console.error("Couldn't start geofence monitoring:", error));

 

Step 4: Setting Up Service Worker and Push Notifications

 

To send notifications when the app isn't active, you need a service worker:

 

// In your main application code
function registerServiceWorker() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/geofence-sw.js')
      .then(registration => {
        console.log('ServiceWorker registered with scope:', registration.scope);
        return requestNotificationPermission();
      })
      .catch(error => {
        console.error('ServiceWorker registration failed:', error);
      });
  }
}

function requestNotificationPermission() {
  return new Promise((resolve, reject) => {
    if (!('Notification' in window)) {
      reject("This browser doesn't support notifications");
      return;
    }
    
    if (Notification.permission === 'granted') {
      resolve();
      return;
    }
    
    if (Notification.permission !== 'denied') {
      Notification.requestPermission().then(permission => {
        if (permission === 'granted') {
          resolve();
        } else {
          reject("Notification permission denied");
        }
      });
    } else {
      reject("Notification permission previously denied");
    }
  });
}

// Initialize everything
Promise.all([
  registerServiceWorker(),
  requestLocationPermission()
])
.then(() => {
  const monitor = new GeofenceMonitor(geofences);
  monitor.start();
})
.catch(error => {
  console.error("Setup failed:", error);
  // Show appropriate UI for permission issues
});

 

Now implement the service worker in a separate file (geofence-sw.js):

 

// geofence-sw.js
self.addEventListener('install', event => {
  console.log('Service Worker installing');
  self.skipWaiting(); // Activate immediately
});

self.addEventListener('activate', event => {
  console.log('Service Worker activating');
  return self.clients.claim(); // Take control immediately
});

// Listen for messages from the main application
self.addEventListener('message', event => {
  const data = event.data;
  
  if (data.type === 'GEOFENCE_ENTER') {
    const geofence = data.geofence;
    
    // Show the notification
    self.registration.showNotification(
      geofence.name, 
      {
        body: geofence.message,
        icon: '/images/notification-icon.png',
        badge: '/images/notification-badge.png',
        vibrate: [100, 50, 100],
        data: {
          action: geofence.action
        }
      }
    );
  }
});

// Handle notification clicks
self.addEventListener('notificationclick', event => {
  event.notification.close();
  const action = event.notification.data.action;
  
  event.waitUntil(
    clients.matchAll({type: 'window'}).then(clientList => {
      // If a window client already exists, focus it
      for (const client of clientList) {
        if (client.url.includes(self.location.origin) && 'focus' in client) {
          return client.focus().then(() => {
            if (action) client.navigate(action);
          });
        }
      }
      
      // Otherwise, open a new window
      if (action && clients.openWindow) {
        return clients.openWindow(action);
      }
    })
  );
});

 

Step 5: Connecting the Geofence Monitor to the Notification System

 

Now, update the GeofenceMonitor to send notifications through the service worker:

 

// Update the GeofenceMonitor class with this method
sendNotification(geofence) {
  // Check if service worker is active and notification permission granted
  if (!('serviceWorker' in navigator) || 
      Notification.permission !== 'granted') {
    console.warn("Can't send notification - missing permissions or service worker");
    return;
  }
  
  // Store this entry in local storage to prevent duplicate notifications
  const notificationKey = `notified_${geofence.id}`;
  const lastNotified = localStorage.getItem(notificationKey);
  const now = Date.now();
  
  // Don't notify for the same geofence more than once every 24 hours
  if (lastNotified && (now - parseInt(lastNotified)) < 24 * 60 * 60 * 1000) {
    console.log(`Skipping notification for ${geofence.name} - already notified recently`);
    return;
  }
  
  // Send message to service worker
  navigator.serviceWorker.ready.then(registration => {
    localStorage.setItem(notificationKey, now.toString());
    
    if (registration.active) {
      registration.active.postMessage({
        type: 'GEOFENCE_ENTER',
        geofence: geofence
      });
    }
  });
}

 

Advanced Considerations

 

Battery Life Optimization

 

Continuous location tracking can drain batteries quickly. Here's how to optimize:

 

// Add this to your GeofenceMonitor class
optimizeTracking() {
  // Start with high-frequency updates when app is in use
  const foregroundOptions = {
    enableHighAccuracy: true,
    maximumAge: 10000,
    timeout: 15000
  };
  
  // Use less frequent updates when app is in background
  const backgroundOptions = {
    enableHighAccuracy: false,
    maximumAge: 60000,
    timeout: 30000
  };
  
  // Use visibility API to detect when page is hidden/visible
  document.addEventListener('visibilitychange', () => {
    if (this.watchId !== null) {
      // Restart tracking with appropriate options
      navigator.geolocation.clearWatch(this.watchId);
      
      const options = document.hidden ? backgroundOptions : foregroundOptions;
      this.watchId = navigator.geolocation.watchPosition(
        this.handlePositionUpdate.bind(this),
        this.handleError.bind(this),
        options
      );
    }
  });
}

 

Handling Poor Connectivity

 

Background Sync API ensures notifications are processed even if connectivity is spotty:

 

// In your service worker (geofence-sw.js)
self.addEventListener('sync', event => {
  if (event.tag === 'geofence-sync') {
    event.waitUntil(
      // Process any queued geofence events
      getPendingGeofenceEvents().then(events => {
        return Promise.all(events.map(processGeofenceEvent));
      })
    );
  }
});

// Helper functions for managing queued events
function queueGeofenceEvent(event) {
  return idb.open('geofence-db').then(db => {
    const tx = db.transaction('events', 'readwrite');
    tx.objectStore('events').put(event);
    return tx.complete;
  });
}

function getPendingGeofenceEvents() {
  return idb.open('geofence-db').then(db => {
    return db.transaction('events')
      .objectStore('events')
      .getAll();
  });
}

function processGeofenceEvent(event) {
  // Process the event, then remove from queue
  // ...
}

 

Example Implementation: A Retail Use Case

 

Here's how all these pieces come together in a practical retail application:

 

// Initialize the geofences for a retail chain
const retailStores = [
  {
    id: "nyc-flagship",
    name: "NYC Flagship Store",
    lat: 40.7128,
    lng: -74.0060,
    radius: 500,
    message: "Visit our NYC store today and get 20% off your next purchase!",
    action: "/stores/nyc/offers"
  },
  // Additional stores...
];

// Customize for retail-specific behavior
class RetailGeofenceMonitor extends GeofenceMonitor {
  constructor(geofences, userPreferences) {
    super(geofences);
    this.userPreferences = userPreferences; // User's notification preferences
    this.cart = null; // Will hold abandoned cart info
  }
  
  // Override to add retail-specific logic
  triggerGeofenceEnter(geofence) {
    // Get the user's shopping cart if they've abandoned one
    fetch('/api/user/cart')
      .then(response => response.json())
      .then(cart => {
        this.cart = cart;
        
        // Customize notification based on cart contents and location
        if (cart && cart.items && cart.items.length > 0) {
          // Modify the geofence message to mention cart items
          const customGeofence = {...geofence};
          customGeofence.message = `You have ${cart.items.length} items in your cart. Visit our ${geofence.name} to complete your purchase!`;
          
          this.sendNotification(customGeofence);
        } else {
          // Standard notification for users without items in cart
          this.sendNotification(geofence);
        }
      })
      .catch(error => {
        console.error("Failed to fetch cart:", error);
        // Fall back to standard notification
        this.sendNotification(geofence);
      });
  }
}

// Usage
document.addEventListener('DOMContentLoaded', () => {
  // Get user preferences from profile
  fetch('/api/user/preferences')
    .then(response => response.json())
    .then(preferences => {
      // Only start if user has opted in to location features
      if (preferences.locationEnabled) {
        Promise.all([
          registerServiceWorker(),
          requestLocationPermission()
        ])
        .then(() => {
          const retailMonitor = new RetailGeofenceMonitor(retailStores, preferences);
          retailMonitor.start();
          retailMonitor.optimizeTracking();
        })
        .catch(error => {
          console.error("Setup failed:", error);
          // Show UI for enabling permissions
          document.getElementById('enable-location').style.display = 'block';
        });
      } else {
        // Show UI for opting into the feature
        document.getElementById('location-benefits').style.display = 'block';
      }
    });
});

 

Performance Metrics and Business Impact

 

What to Measure

 

Once implemented, track these key performance indicators:

 

  • Conversion rate - Percentage of users who take action after receiving a geofence notification
  • Engagement lift - Increase in app engagement for users who receive geofenced notifications vs. those who don't
  • Battery impact - Monitor how your implementation affects device battery consumption
  • Notification-to-visit ratio - How many notifications actually result in physical store visits

 

Real-world impact: Retail clients implementing similar systems have seen a 23% increase in abandoned cart recovery and a 15% lift in store visit frequency among their most engaged app users.

 

Conclusion

 

Geofencing notifications represent one of those rare features that bridges the digital-physical divide, creating contextually relevant experiences that feel almost magical to users.

 

While implementation requires careful consideration of location services, permissions, and battery usage, the technical complexity is manageable with modern web APIs. The key is finding the right balance between technical capability and user experience—notifications frequent enough to be valuable but not so persistent they become annoying.

 

When done right, geofencing can transform passive web app users into active customers engaging with your business in physical locations—all through the power of well-timed, contextually relevant notifications.

Ship Geofencing 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 Geofencing Notifications Usecases

Explore the top 3 practical geofencing notification use cases to boost your web app’s engagement and user experience.

 

Retail Customer Engagement

 
  • Trigger personalized offers when customers enter your physical store radius, creating seamless online-to-offline experiences. This converts digital browsing into in-store purchases by sending timely, context-aware promotions exactly when customers are most likely to act.
  •  
  • Research shows conversion rates increase by 65-70% when notifications arrive at the moment of physical proximity compared to random timing. For retail businesses, this represents the digital equivalent of a store greeter with perfect memory of each customer's preferences.

 

Safety & Logistics Management

 
  • Automate critical alerts for fleet vehicles entering/exiting designated operational zones, enabling real-time monitoring of asset movement without manual tracking. This creates accountability through automated timestamps and ensures compliance with delivery schedules.
  •  
  • For high-value shipments or personnel working in remote locations, geofencing provides essential safety boundaries that trigger automated escalation protocols when breached. This transforms standard location data into proactive risk management.

 

Smart Home/Office Automation

 
  • Trigger environment controls when users approach their homes or offices, activating lighting, temperature adjustments, and security system changes without manual intervention. This creates an intelligent workspace that responds to presence rather than requiring explicit commands.
  •  
  • Beyond convenience, this automation reduces energy consumption by 15-30% by ensuring systems only run when needed. The technology essentially creates a digital membrane around physical spaces that responds to authorized users while maintaining security protocols.


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