---
title: Overview
description: 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](https://datatracker.ietf.org/doc/html/rfc8030) with [VAPID](https://datatracker.ietf.org/doc/html/rfc8292) 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:

```bash
npx web-push generate-vapid-keys
```

This 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:

```javascript
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](/docs/concepts/channel-subscriptions) API:

```javascript
// 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:

```javascript
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](/docs/learn/building-notiflows/templates) 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 }}` |
