Overview
Web push notifications using the Web Push standard (VAPID)
Web Push enables you to send push notifications directly to web browsers using the Web Push Protocol with VAPID authentication, even when users aren't actively on your site.
Supported Browsers
- Chrome
- Firefox
- Safari
- Edge
Setup
1. Generate VAPID Keys
Generate a VAPID key pair for your application. You can use any standard tool:
npx web-push generate-vapid-keysThis produces a public key and a private key. Keep the private key secret.
2. Create a Web Push Channel
In your Notiflows dashboard, go to Channels and create a new Web Push channel with the Web Push (VAPID) provider. You'll need to provide:
| Field | Description |
|---|---|
| VAPID Public Key | The base64url-encoded public key from step 1 |
| VAPID Private Key | The base64url-encoded private key from step 1 |
| VAPID Subject | A contact URI — either mailto:admin@yourapp.com or https://yourapp.com |
The VAPID subject identifies your application server to push services. It must be a valid mailto: address or https:// URL.
3. Add a Service Worker
Your application needs a service worker to handle incoming push events and display notifications. Create a sw.js file at the root of your site:
self.addEventListener('push', (event) => {
const data = event.data?.json() ?? {};
const title = data.title || 'New Notification';
const options = {
body: data.body || '',
icon: data.icon,
image: data.image,
data: { actionUrl: data.action_url },
};
event.waitUntil(self.registration.showNotification(title, options));
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const actionUrl = event.notification.data?.actionUrl;
if (actionUrl) {
event.waitUntil(clients.openWindow(actionUrl));
}
});4. Register Push Subscriptions
On the client side, use the browser's Push API to subscribe users and send the subscription to Notiflows via the Channel Subscriptions API:
// Register the service worker
const registration = await navigator.serviceWorker.register('/sw.js');
await navigator.serviceWorker.ready;
// Request permission and subscribe
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: '<your-vapid-public-key>',
});
// Send the subscription to Notiflows via the User API
const { endpoint, keys } = subscription.toJSON();
await fetch('https://api.notiflows.com/user/v1/channels/<channel-id>/subscriptions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-notiflows-api-key': '<your-public-api-key>',
'x-notiflows-user-key': '<user-jwt-token>',
},
body: JSON.stringify({
identifier: endpoint,
settings: {
endpoint,
p256dh: keys.p256dh,
auth: keys.auth,
},
}),
});The applicationServerKey must be the same VAPID public key configured on your channel.
Users can subscribe from multiple browsers or devices. Notiflows deduplicates by identifier and delivers to all registered subscriptions. It is safe to call this on every page load — if the subscription already exists, the settings are updated in place.
5. Handle Unsubscription
When a user opts out of push notifications, unsubscribe them from the browser and remove the subscription from Notiflows:
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.getSubscription();
if (subscription) {
const { endpoint } = subscription.toJSON();
// Remove from Notiflows
await fetch('https://api.notiflows.com/user/v1/channels/<channel-id>/subscriptions', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'x-notiflows-api-key': '<your-public-api-key>',
'x-notiflows-user-key': '<user-jwt-token>',
},
body: JSON.stringify({ identifier: endpoint }),
});
// Unsubscribe the browser
await subscription.unsubscribe();
}Templates
Web push templates support the following fields:
| Field | Required | Format | Description |
|---|---|---|---|
| Title | Yes | Plaintext | The notification heading |
| Body | Yes | Plaintext | The notification body text |
| Icon URL | No | URL | Small image displayed next to the title (typically your app logo) |
| Image URL | No | URL | Large image displayed in the notification body |
| Action URL | No | URL | URL opened when the user clicks the notification |
Title and body are always plaintext — this is a browser limitation. All fields support Liquid templating for dynamic content.
Example:
| Field | Value |
|---|---|
| Title | New message from {{ actor.first_name }} |
| Body | {{ data.message_preview }} |
| Icon URL | https://yourapp.com/icon.png |
| Action URL | https://yourapp.com/messages/{{ data.message_id }} |