Skip to main content
Web push notifications let you reach users with timely, relevant messages even when they aren’t actively browsing your site. The Tappd Web SDK uses the native Web Push API with VAPID keys, so notifications work across all modern browsers with a single integration.

Prerequisites

Before you configure push notifications, make sure you have the following in place:
  • HTTPS: Web push requires a secure origin (HTTPS). Localhost is exempt during development.
  • Service worker: A sw.js file must be accessible at the root of your domain.
  • VAPID keys: Generated automatically in the Tappd dashboard — no manual key management needed.
  • Identified users: Users must be identified with identify() before you subscribe them.

Browser Support

BrowserSupport
Chrome (Desktop & Android)
Firefox (Desktop & Android)
Edge
Safari 16.4+ (macOS & iOS)
Opera
Safari < 16.4❌ Not supported
Safari 16.4+ uses the standard Web Push API with VAPID keys — the same flow as Chrome and Firefox. No separate Apple certificate is required for modern Safari.

Setup

1

Configure Web Push in the Dashboard

  1. Log into your Tappd Dashboard.
  2. Navigate to Settings > Apps and select your web app.
  3. Go to Push Configuration > Web Push.
  4. Toggle Web Push on.
  5. Click Generate Keys to create your VAPID key pair.
  6. Optionally set a Default Icon URL and Site Name for your notifications.
2

Create the Service Worker

Create a file named sw.js at the root of your domain (e.g., https://yourdomain.com/sw.js). Copy the following code into it:
// sw.js
self.addEventListener('push', function(event) {
  const data = event.data ? event.data.json() : {};

  const options = {
    body: data.body || '',
    icon: data.icon || '/icon-192x192.png',
    badge: data.badge || '/badge-72x72.png',
    image: data.image,
    data: data.data || {},
    requireInteraction: data.requireInteraction || false,
    tag: data.tag,
    renotify: data.renotify || false,
    silent: data.silent || false,
    timestamp: data.timestamp || Date.now(),
    vibrate: data.vibrate || [200, 100, 200],
    actions: data.actions || []
  };

  event.waitUntil(
    self.registration.showNotification(data.title || 'Notification', options)
  );
});

// Handle notification clicks and auto-track opens
self.addEventListener('notificationclick', function(event) {
  event.notification.close();

  const data = event.notification.data || {};
  const url = data.url || '/';

  // Fire-and-forget tracking to the Tappd SDK API
  const trackPromise = fetch('https://sdk.gotappd.com/api/v1/sdk/push/interactions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-App-Id': data.appId || data.tappd_app_id
    },
    body: JSON.stringify({
      type: 'opened',
      deliveryLogId: data.tappd_delivery_log_id,
      enrollmentId: data.tappd_enrollment_id,
      journeyId: data.tappd_journey_id,
      stepId: data.tappd_step_id,
      customerId: data.tappd_customer_id,
      providerMessageId: data.providerMessageId
    })
  }).catch(function(error) {
    console.error('Failed to track push open:', error);
  });

  event.waitUntil(
    Promise.all([
      trackPromise,
      clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function(clientList) {
        for (let i = 0; i < clientList.length; i++) {
          const client = clientList[i];
          if (client.url === url && 'focus' in client) {
            return client.focus();
          }
        }
        if (clients.openWindow) {
          return clients.openWindow(url);
        }
      })
    ])
  );
});
The service worker file must be served from the root path (/sw.js). A file at /assets/sw.js will not have the correct scope to intercept push events.
3

Identify the User and Subscribe

Users must be identified before you subscribe them to push notifications. Call identify() first, then subscribeToPush():
import { TappdSDK } from '@tappd/web-sdk';

const tappd = new TappdSDK({
  appId: 'YOUR_APP_ID',
  apiUrl: 'https://sdk.gotappd.com/api/v1/sdk'
});

// Identify the user — required before subscribing
await tappd.identify({
  external_id: 'user_123',
  email: 'john@example.com',
  name: 'John Doe'
});

// Subscribe to push notifications
try {
  const subscription = await tappd.subscribeToPush();
  console.log('Successfully subscribed to push notifications');
} catch (error) {
  console.error('Failed to subscribe:', error);
}

Checking and Managing Subscriptions

Subscribe a User

Call subscribeToPush() after identifying the user. It accepts an optional options object:
// Basic subscription — uses your dashboard VAPID configuration
const subscription = await tappd.subscribeToPush();

// With options — override the service worker path if needed
const subscription = await tappd.subscribeToPush({
  serviceWorkerPath: '/sw.js'  // Path to your service worker (default: '/sw.js')
});

Check Subscription Status

Before prompting the user, check whether they’re already subscribed:
const isSubscribed = await tappd.isSubscribed();

if (isSubscribed) {
  console.log('User is already subscribed');
} else {
  await tappd.subscribeToPush();
}

Unsubscribe a User

Give users a clear way to opt out:
async function unsubscribeFromPush() {
  try {
    await tappd.unsubscribeFromPush();
    console.log('Successfully unsubscribed');

    await tappd.track('push_notification.unsubscribed');
  } catch (error) {
    console.error('Failed to unsubscribe:', error);
  }
}

document.getElementById('disable-notifications').addEventListener('click', unsubscribeFromPush);

Permission Prompt Types

Choose the prompt style that fits your UX. You can configure the default in the dashboard or override it in code.
A customizable slide-down banner appears at the top of the page, letting you explain the value of notifications before the browser prompt appears.
await tappd.showPermissionPrompt('slidedown');

Sending Notifications

From the Dashboard

Navigate to Templates > Push Templates, create or select a template, then attach it to a Journey. Target the Journey at specific customers or segments to send.

From the API

Use the Tappd Management API to send notifications programmatically from your backend.
const response = await fetch(
  'https://api.gotappd.com/v1/push/send',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_API_KEY'
    },
    body: JSON.stringify({
      customerId: 'customer_id',
      appId: 'app_id',
      notification: {
        title: 'Hello!',
        body: 'This is a test notification',
        url: 'https://example.com',
        icon: 'https://example.com/icon.png'
      }
    })
  }
);

Testing

Send a test notification directly from the dashboard:
  1. Go to Settings > Apps > [Your App].
  2. Scroll to Web Push Configuration.
  3. Click Test Web Push.
  4. Select a customer and fill in the notification details.
  5. Click Send Test Push.

Troubleshooting

Likely causes:
  • The sw.js file is not served from the domain root (/sw.js).
  • The page is not served over HTTPS (required in production).
  • The service worker file URL returns a 404.
Fix: Open https://yourdomain.com/sw.js in your browser to confirm the file is accessible. Check the browser console for registration errors.
Once a user denies browser-level notification permission, the browser blocks future prompts from that origin.Fix: Use a custom UI prompt to explain the value of notifications before triggering the browser dialog. This gives users the context they need to accept. If denied, surface a settings link so users can re-enable notifications at their own pace.
Work through this checklist:
  1. Call await tappd.isSubscribed() and confirm it returns true.
  2. Verify VAPID keys are generated and saved in the dashboard.
  3. Confirm the service worker is active — check the Application > Service Workers panel in DevTools.
  4. Ensure identify() was called with a valid external_id before subscribing.
  5. Verify the notification was actually sent from the dashboard or API.
Push subscriptions can expire or be revoked by the browser.Fix: Poll isSubscribed() periodically and re-subscribe when needed:
// Check daily and re-subscribe if the subscription has lapsed
setInterval(async () => {
  const isSubscribed = await tappd.isSubscribed();
  if (!isSubscribed) {
    try {
      await tappd.subscribeToPush();
    } catch (error) {
      console.error('Re-subscription failed:', error);
    }
  }
}, 24 * 60 * 60 * 1000); // Every 24 hours