> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sequenzy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Push notifications

> Send web push to browsers and mobile push to your iOS and Android apps from campaigns and sequences, with device registration, click tracking, and delivery stats

# Push notifications

Push notifications reach contacts on their devices even when they are not
reading email. Sequenzy sends three kinds of push from one place:

| Platform | How it's delivered | What you set up |
| - | - | - |
| **Web** | Browser push (Chrome, Edge, Firefox, Safari on macOS, and Safari on iOS 16.4+ when your site is installed to the home screen) | Turn on web push and add one file to your site |
| **iOS** | Apple Push Notification service (APNs) | Upload your APNs auth key (.p8) and register device tokens from your app |
| **Android** | Firebase Cloud Messaging (FCM) | Upload your Firebase service account and register FCM tokens from your app |

Push is included in every plan. Sending a push does not use email credits.

## How consent works

A device receives push only after the person allowed notifications in their
browser or in your app. Each allowed browser or app install is a **device**
linked to a contact. Push is independent of email: a contact who unsubscribed
from email still receives push on their devices, and a contact with no email
address can still be reached.

A device stops receiving push when:

* The person unsubscribes the browser or you unregister the device.
* The push service reports the token as expired or uninstalled. Sequenzy marks
  the device `invalid` automatically.
* The contact is signed out on a shared browser (see
  [Linking browsers to contacts](#linking-browsers-to-contacts)).

## Web push

### Set it up with an AI coding tool

In `Settings -> Push -> Web`, choose **Copy prompt**. The prompt contains your
site key, the service worker file, the subscribe button and the sign-in steps,
with your values filled in. Paste it into Cursor, Claude Code, or Codex, or send
it to your developer. The **iOS** and **Android** tabs have the same button for
your apps.

### 1. Turn on web push

Go to `Settings -> Push -> Web` and turn on web push. Sequenzy generates your
workspace's VAPID keys. If you have no web tracking key yet and your company
website is set, Sequenzy also creates a site key allowed for that website (and
its `www` or apex twin), so the snippet is ready to paste. Turning web push on through the API, CLI, or MCP does the same and returns the key and snippet. Turning web push off later
keeps the keys, so existing subscribers keep working when you turn it back on.

Notifications use your company logo as the icon. Change it under
**Notification icon** if you want a different one.

### 2. Install the tracking snippet

Push uses the same browser SDK and publishable key as
[web tracking](./web-tracking). If the snippet is already on your site, skip
this step.

### 3. Add the service worker file

Browsers only run service workers from the site that asks for permission.
Serve a file at `/sequenzy-sw.js` on your domain containing:

```js theme={null}
importScripts("https://api.sequenzy.com/sequenzy-sw.js");
```

The settings page has a **Download sequenzy-sw\.js** button. Put the file in the
folder served from your site root, such as `public/` in Next.js, Vite, Astro,
or Nuxt. The file never needs updating: the hosted worker shows Sequenzy
notifications, reports when they are displayed and clicked, and opens the
link. It ignores pushes from other senders.

If your site already has a service worker at its root (for example a PWA or
Workbox setup), add the `importScripts` line to that worker instead of serving
`/sequenzy-sw.js`. A site has one service worker per scope, so the SDK
subscribes through the worker that already controls your pages, and pushes
arrive there.

### 4. Add a subscribe button

Browsers block permission prompts that the visitor did not trigger, and a
declined prompt cannot be shown again. Add a button with the
`data-sequenzy-push` attribute:

```html theme={null}
<button data-sequenzy-push hidden>Get notifications</button>
```

No script is needed. Once the SDK loads, it shows the button only in browsers
that can subscribe, asks for permission when it is clicked, and hides it again
once the browser is subscribed, blocked, or web push is off. Buttons rendered
later by a single-page app work too.

To build your own flow instead, call `subscribeToPush()` from a click. The
tracking snippet loads the SDK asynchronously, so push methods only work after
the page's `load` event:

```html theme={null}
<button id="get-notifications" hidden>Get notifications</button>
<script>
  window.addEventListener("load", () => {
    if (typeof sequenzy.isPushSupported !== "function") return;
    if (!sequenzy.isPushSupported()) return;
    const button = document.getElementById("get-notifications");
    button.hidden = false;
    button.onclick = () => sequenzy.subscribeToPush();
  });
</script>
```

With a bundler:

```ts theme={null}
import { createBrowserClient } from "@sequenzy/web-sdk";

const sequenzy = createBrowserClient({
  publicKey: "seq_pk_your_key",
  companyId: "your_workspace_id",
});

const result = await sequenzy.subscribeToPush();
// { status: "subscribed", deviceId } | { status: "denied" } |
// { status: "unsupported" } | { status: "disabled" } | { status: "error", error }
```

`isPushSupported()` and `getPushPermission()` let you decide whether to show
the button at all. Pass `{ serviceWorkerPath: "/path/to/sw.js" }` if your
worker lives somewhere other than `/sequenzy-sw.js`.

### Linking browsers to contacts

A browser that subscribes before the visitor signs in is anonymous and does
not receive campaigns yet. When your site calls
`sequenzy.identify(email, identityToken)` (see
[web tracking](./web-tracking#step-3-identify-your-visitors)), the SDK re-registers the
subscription with that identity and the device is linked to the contact. The
SDK also refreshes the registration once a day and whenever the browser's
push endpoint changes.

When a visitor signs out, call `sequenzy.reset()`. The SDK unregisters the
browser and drops the contact link, so the next person on a shared computer
does not receive the previous person's notifications.

## iOS and Android apps

### Credentials

In the **iOS** and **Android** tabs of `Settings -> Push`, **Copy prompt** gives
your AI coding tool or developer the app-side steps below with your bundle ID or
Firebase project filled in. Then add credentials:

* **iOS**: upload the `.p8` auth key from Apple Developer (Keys, with Apple
  Push Notifications service enabled) and enter the key ID, team ID, and bundle
  ID. Choose **Sandbox** for Xcode development builds and **Production** for
  App Store and TestFlight builds.
* **Android**: upload the service account JSON from Firebase project settings
  (Service accounts, Generate new private key). The account needs the Firebase
  Cloud Messaging API Admin role.

Keys are encrypted and never shown again. If Apple or Google rejects them, the
settings page shows the error until a send succeeds or you replace the key.

### Register device tokens

Your app gets a token from APNs or Firebase Messaging. Send it to your backend
and register it with [Register a push device](../api-reference/push/register-device):

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/push/devices" \
  -H "Authorization: Bearer seq_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "ios",
    "token": "8f2c9e...",
    "email": "jane@example.com",
    "deviceName": "Jane iPhone",
    "appVersion": "3.2.0"
  }'
```

Register the token again whenever the app receives a new one; the same token
is updated rather than duplicated, and moves to the contact you pass. When a
person signs out of your app, call
[Unsubscribe a push device](../api-reference/push/delete-device). Each contact
keeps up to 20 active devices; older ones are retired automatically.

### What your app receives

Each notification carries a `sequenzy` object: in the APNs payload next to
`aps`, and as string fields in the FCM `data` map.

```json theme={null}
{
  "aps": {
    "alert": { "title": "Your order shipped", "body": "Tap to track it" },
    "sound": "default"
  },
  "sequenzy": {
    "v": 1,
    "title": "Your order shipped",
    "body": "Tap to track it",
    "url": "myapp://orders/123",
    "imageUrl": "https://cdn.example.com/box.png",
    "pushSendId": "push_abc",
    "deviceId": "dev_123",
    "clickToken": "Qm9...",
    "eventsUrl": "https://api.sequenzy.com/api/v1/push/events"
  }
}
```

* Open `url` when the person taps the notification. It can be an https link or
  one of your app's deep links.
* iOS shows images only through a Notification Service Extension that
  downloads `imageUrl`. Sequenzy sets `mutable-content` whenever an image is
  present.
* To report opens, POST to `eventsUrl` from the app:

```json theme={null}
{
  "pushSendId": "push_abc",
  "deviceId": "dev_123",
  "token": "Qm9...",
  "type": "clicked"
}
```

Use `"type": "delivered"` from a Notification Service Extension (iOS) or when
your Android app handles the message, to count displays. The token
authenticates the report, so no API key belongs in your app.

## Writing a push

| Field | Notes |
| - | - |
| Title | Up to 120 characters. Most devices show about 40. |
| Message | Up to 500 characters. Most devices show about 120. |
| Link on tap | An https URL, an app deep link, or a merge tag that resolves to one. Browsers ignore deep links. |
| Image | Optional https image. |
| Icon | Optional https icon for web push. Defaults to the icon in push settings, then your company logo. |
| Platforms | Web, iOS, and Android by default. Limit a message to some platforms if the copy only fits one. |

Merge tags work in the title, message, and link, for example
`Hi {{FIRST_NAME|there}}`. If a merged message grows past the 4 KB limit push
services accept, Sequenzy shortens the message rather than failing the send.

## Push campaigns

Open `Campaigns`, switch to **Push**, and choose **New Push Campaign**. Write
the notification, pick the audience, then send now or schedule it. Only
contacts with an active device on a selected platform receive the campaign;
the recipients step shows how many that is. The audience is re-evaluated when
delivery starts, so contacts who subscribe before a scheduled send are
included.

A contact receives one notification per active device. If a push service is
temporarily unavailable, Sequenzy retries only the devices that have not
accepted the message yet.

## Segments

Push filters appear in the segment builder once any platform is set up:
`pushDevice` (has an active device, on any platform or a specific one) and
`pushSent`, `pushDelivered`, and `pushClicked` with a time window such as
`30d` or `all`. For example, combine `pushDevice is ios` with
`pushClicked is_not 30d` to find iOS users who stopped opening your
notifications. See [segment filters](./subscribers#push-filters).

## Push steps in sequences

Add a **Send Push** step anywhere in a sequence. If the contact has no active
device, the step is skipped by default; set **If the contact has no push
device** to **Exit the sequence** to stop the journey instead. A workspace
without push set up always skips the step, so a missing key never exits
contacts in bulk.

## Stats

| Metric | Meaning |
| - | - |
| Sent | Contacts whose notification at least one push service accepted |
| Devices | Devices that accepted, out of devices targeted |
| Displayed | Contacts whose device reported showing it. Browsers report this automatically; apps report it through `eventsUrl` |
| Clicked | Contacts who opened the notification |
| Failed | Every device rejected the push, or the credentials were refused |
| Skipped | No active device on a set-up platform, or the campaign was stopped |

Test sends are excluded from stats and from contact activity.

## Test sends

`Settings -> Push -> Send a test push` sends to one device or to every device
of a contact. You can send 200 test pushes per workspace in a rolling 24 hours.

## API, CLI, and MCP

Everything in the dashboard is also available programmatically:

* **API**: [push settings](../api-reference/push/settings), web, iOS, and
  Android credentials, [devices](../api-reference/push/list-devices),
  [test sends](../api-reference/push/test), and
  [push campaigns](../api-reference/push/create-campaign). Sequence steps use
  `type: "push"` in [Create a sequence](../api-reference/sequences/create).
* **CLI**: `sequenzy push settings`, `push web --enable`, `push ios set`,
  `push android set`, `push devices list|register|remove`, `push send-test`,
  and `push campaigns estimate|create|update|send|cancel|stats`.
* **MCP**: `get_push_settings`, `register_push_device`, `send_test_push`,
  `create_push_campaign`, `send_push_campaign`, and the other push tools listed
  in [MCP](./mcp#push-notifications).

Push campaign scopes reuse the campaign API key scopes (`campaigns:read`,
`campaigns:write`, `campaigns:send`); test sends need `campaigns:send`;
devices use `subscribers:read` and `subscribers:write`; reading push settings
needs `account:read` and changing credentials needs `integrations:manage`.

## Browser endpoints

The web SDK calls these public endpoints with your publishable key and the
allowed origins configured on it. You only need them if you build your own
subscription flow:

| Endpoint | Purpose |
| - | - |
| `GET /api/v1/push/web-config?companyId=&publicKey=` | Returns whether web push is on and the VAPID public key |
| `POST /api/v1/push/web-subscriptions` | Registers a `PushSubscription` (`subscription`, optional `anonymousId`, and `email` plus `identityToken` to link a contact) |
| `POST /api/v1/push/web-subscriptions/remove` | Unregisters an endpoint and drops its contact link |
| `POST /api/v1/push/events` | Records `delivered` and `clicked` reports authenticated by the notification's token |

Send bodies as JSON with `Content-Type: text/plain` to avoid a CORS preflight.

## Troubleshooting

* **Nobody is eligible**: check `Settings -> Push` for device counts. Browsers
  that subscribed before sign-in stay anonymous until your site calls
  `identify`.
* **iOS sends fail with "Key rejected"**: the key ID, team ID, bundle ID, or
  environment does not match the app build. Development builds need
  **Sandbox**.
* **Android sends fail with "Key rejected"**: the service account is missing the
  Firebase Cloud Messaging API Admin role, or the key was deleted in Google
  Cloud.
* **A browser never prompts**: `subscribeToPush()` must run from a click, the
  page must be https, and the visitor must not have blocked notifications for
  your site.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.