Websites and PWAs (Web Push)

Notifications in Chrome, Edge, Firefox, Safari and Opera with the web SDK and a service worker.

With Web Push your site sends notifications to the user's browser, even when the tab is closed (as long as the browser is open). It works in Chrome, Edge, Opera, Brave and Samsung Internet (Android and desktop), Firefox and Safari (macOS 13+). On iPhone and iPad it only works if the user added your site to the home screen (iOS 16.4+).

The browser delivers the encrypted message through its push service (Google, Mozilla, Apple or Microsoft); Pushtifly encrypts it for each browser and signs it with your VAPID keys. You don't need Firebase.

1. Set up the app in the panel

  1. In the app, under Platforms, select Web (browsers).
  2. In Credentials → Web Push, type a contact (mailto:support@yourcompany.com or an https URL) and click Generate keys.
    • Already using another service with your own VAPID keys? Use Import keys with the public and private key (base64url): browsers that are already subscribed keep receiving.
  3. In API keys, create a public key (pk_live_…) restricted to this app.

2. Install the SDK

npm install @daertech/pushtifly-web

Copy node_modules/@daertech/pushtifly-web/dist/pushtifly-sw.js to your site's public root (so it's served at https://yoursite.com/pushtifly-sw.js). The browser only accepts a service worker from your own domain.

No bundler? Use dist/pushtifly.js with a <script> tag: it exposes PushtiflyWeb.Pushtifly.

3. Start it and ask for permission

import { Pushtifly } from '@daertech/pushtifly-web';

const pushtifly = new Pushtifly({ appKey: 'pk_live_…' });
await pushtifly.init(); // registers /pushtifly-sw.js; if the user already granted permission, it syncs the subscription

// Ask for permission after a user gesture: browsers block automatic prompts
enableButton.addEventListener('click', async () => {
  if ((await pushtifly.requestPermission()) === 'granted') {
    console.log('Browser registered', pushtifly.state().deviceId);
  }
});

Best practices so people accept the permission: explain first what you'll send (your own banner with an "Enable" button) and don't ask for it on page load.

4. Users, tags and topics

await pushtifly.identify('customer-123');  // on sign-in
await pushtifly.setTags({ plan: 'gold' });
await pushtifly.addTopic('offers');
await pushtifly.logout();                  // on sign-out
await pushtifly.unsubscribe();             // the user doesn't want notifications anymore

5. When the notification is tapped

The service worker opens the target URL (the send's or the button's deep link; it can be relative to your site) and reports the open or click. If your site is open, you get the notification:

pushtifly.onNotificationClick((n) => console.log(n.id, n.url, n.actionId, n.data));
pushtifly.onData((data) => { /* data-only sends while the page is open */ });

What's shown

Send field In the browser
Title and text (with the subtitle before the text) title and body
The send's large icon, or the app's icon icon
Image (or the poster of a GIF or video) image (Chrome and Edge on desktop and Android)
Deep link URL opened on tap
Buttons up to 2 (actions)
High / normal priority Urgency header
TTL how long the push service keeps the message if the browser is closed (maximum 4 weeks)

The encrypted content has a maximum of 3,993 bytes: the composer checks it before sending.

If you already have a service worker

Import Pushtifly's inside yours and give it the configuration:

// your-sw.js
self.PUSHTIFLY_CONFIG = { appKey: 'pk_live_…' };
importScripts('/pushtifly-sw.js');

and on the page: new Pushtifly({ appKey: 'pk_live_…', serviceWorkerRegistration: await navigator.serviceWorker.register('/your-sw.js') }).

With the API, without the SDK

Register the subscription returned by PushManager.subscribe() (with the key from GET /v1/web-push/key):

curl -X POST https://api.pushtifly.com/v1/devices \
  -H "Authorization: Bearer pk_live_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{ "platform": "web", "subscription": { "endpoint": "https://fcm.googleapis.com/fcm/send/…",
        "keys": { "p256dh": "BN…", "auth": "q1…" } } }'

The service worker receives this JSON (event.data.json()): _phid (the send's id), title, body, icon, image, url, actions ([{ action, title, url }]), tag, data (your data) and silent (data-only send). To report opens, call POST /v1/events with token = the subscription's endpoint. Since the token is a URL, the /v1/devices/{token} routes also accept the device's id.

Common problems

  • The permission shows as "blocked": the user denied it; only they can change it from the site settings in their browser.
  • Nothing arrives on iPhone: the site must be added to the home screen and opened from there.
  • VAPID_KEY_MISMATCH: that subscription was created with other keys; the SDK recreates it when the site is opened.
  • SUBSCRIPTION_GONE: the browser dropped the subscription (permission removed or data cleared); Pushtifly marks the browser as invalid.