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

# Pass notifications

> Send push messages to issued Apple Wallet and Google Wallet passes via the API: immediate or scheduled, with per-platform delivery tracking.

Pass notifications push a short message to the wallet passes a holder has installed. One API call reaches both platforms:

* **Apple Wallet** shows a lock-screen banner with your message and stores it in a dedicated **Notification** back field on the pass, where it stays until the next notification replaces it.
* **Google Wallet** shows a push notification and appends the message to the pass's message list (the newest 10 are kept).

Notifications are API-only and require the `pass-notifications:write` scope ([access tokens](/developers/access-tokens)).

## Send a notification

The pass must be `ISSUED`. Delivery is asynchronous: the call returns the notification in `PENDING` status and a background worker delivers it to each platform.

```bash theme={null}
curl -X POST "https://api.passlet.io/v1/passes/$PASS_ID/notifications" \
  -H "Authorization: Bearer $PASSLET_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Doors open at 18:30 today.",
    "idempotencyKey": "doors-open-2026-07-23",
    "options": { "google": { "header": "Schedule update" } }
  }'
```

* `body` (required, 1–1000 chars) is the message shown on both platforms.
* `platforms` (optional, defaults to `["apple", "google"]`) selects the wallet platforms to deliver to; see [Target a single platform](#target-a-single-platform).
* `idempotencyKey` (optional) makes retries safe: reusing a key returns the existing notification with `200` instead of sending a duplicate.
* `options.google.header` (optional) sets the title above the message in the Google Wallet app. Provider-specific inputs always live under `options`.

## Target a single platform

By default a notification goes to both platforms. Set `platforms` to reach only one, for example a message that only makes sense on Apple Wallet:

```bash theme={null}
curl -X POST "https://api.passlet.io/v1/passes/$PASS_ID/notifications" \
  -H "Authorization: Bearer $PASSLET_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "body": "Doors open at 18:30 today.", "platforms": ["apple"] }'
```

The platform that was not targeted reports `SKIPPED` and never affects the overall `status`. Only google-targeted notifications count against the [rate limit](#rate-limit); notifications targeting just Apple are not rate limited.

## Schedule for later

Set `sendAt` to a future time to schedule the send. The notification stays in `SCHEDULED` status until then and can still be [canceled](#cancel-a-scheduled-notification).

```bash theme={null}
curl -X POST "https://api.passlet.io/v1/passes/$PASS_ID/notifications" \
  -H "Authorization: Bearer $PASSLET_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "body": "Doors open in one hour!", "sendAt": "2026-07-23T16:30:00Z" }'
```

## Track delivery

Poll the notification (scope `pass-notifications:read`) to observe progress. The top-level `status` is derived from the per-platform results:

| `status`    | Meaning                                       |
| ----------- | --------------------------------------------- |
| `SCHEDULED` | `sendAt` is in the future, nothing dispatched |
| `PENDING`   | Delivery in progress                          |
| `SENT`      | Delivered on every applicable platform        |
| `PARTIAL`   | One platform sent, the other failed           |
| `FAILED`    | Failed on every applicable platform           |
| `SKIPPED`   | No targeted platform was applicable           |

`apple` and `google` carry the per-platform detail (`PENDING | SENT | FAILED | SKIPPED` plus the last error). A platform that was not targeted via `platforms` (or on which the pass was never issued) resolves as `SKIPPED` and never drags the overall status down. `apple.deviceCount` is the number of Apple devices that were pushed to; `0` means the pass artifact was updated but no device is registered for updates (e.g. the pass was never added to an iPhone).

```json theme={null}
{
  "id": "0198a7c2-…",
  "passId": "0193f4b1-…",
  "body": "Doors open at 18:30 today.",
  "status": "SENT",
  "apple": { "status": "SENT", "error": null, "deviceCount": 2 },
  "google": { "status": "SENT", "error": null },
  "sendAt": null,
  "createdAt": "2026-07-22T15:42:10Z",
  "updatedAt": "2026-07-22T15:42:14Z"
}
```

`GET /v1/passes/{id}/notifications` lists a pass's notifications newest first with the standard [pagination envelope](/api-reference/pagination-and-filtering).

## Cancel a scheduled notification

A notification can be canceled only while its status is `SCHEDULED`:

```bash theme={null}
curl -X DELETE "https://api.passlet.io/v1/passes/$PASS_ID/notifications/$NOTIFICATION_ID" \
  -H "Authorization: Bearer $PASSLET_API_TOKEN"
```

Returns `204` and removes the notification entirely. Once delivery has started, cancellation returns `409` (`PASS_NOTIFICATION_ALREADY_SENT`): sent notifications are immutable history; send a new one instead.

## Broadcast to a project

`POST /v1/projects/{id}/broadcasts` sends one notification to **every `ISSUED` pass in a project**. The request body is identical to sending to a single pass: `body`, `platforms`, `sendAt`, `idempotencyKey` and `options` all work the same way:

```bash theme={null}
curl -X POST "https://api.passlet.io/v1/projects/$PROJECT_ID/broadcasts" \
  -H "Authorization: Bearer $PASSLET_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Doors open at 18:30 today.",
    "idempotencyKey": "doors-open-2026-07-27",
    "options": { "google": { "header": "Schedule update" } }
  }'
```

A broadcast is the *order to send*, not a notification itself. At dispatch time (`sendAt`, or immediately) Passlet snapshots the project's issued passes and creates one ordinary pass notification per pass. Those per-pass notifications carry **all delivery state**: they appear under `GET /v1/passes/{id}/notifications` with their `broadcastId` set, and each one is tracked exactly as described in [Track delivery](#track-delivery). The broadcast itself only reports its dispatch lifecycle:

| `status`     | Meaning                                                            |
| ------------ | ------------------------------------------------------------------ |
| `SCHEDULED`  | `sendAt` is in the future, fan-out not started                     |
| `PENDING`    | Fan-out in progress                                                |
| `DISPATCHED` | Every per-pass notification created (`passCount` reports how many) |

`GET /v1/projects/{id}/broadcasts/{broadcastId}` polls a single broadcast's dispatch progress, and `GET /v1/projects/{id}/broadcasts` lists a project's broadcasts newest first with the standard [pagination envelope](/api-reference/pagination-and-filtering). A broadcast can be canceled only while `SCHEDULED`:

```bash theme={null}
curl -X DELETE "https://api.passlet.io/v1/projects/$PROJECT_ID/broadcasts/$BROADCAST_ID" \
  -H "Authorization: Bearer $PASSLET_API_TOKEN"
```

Returns `204` and removes the broadcast entirely; once fan-out has started it returns `409` (`PASS_NOTIFICATION_BROADCAST_ALREADY_DISPATCHED`).

Broadcasts are not checked against the per-pass Google [rate limit](#rate-limit) at creation; a pass already at its cap resolves the google delivery on its own notification as `FAILED`, without affecting the other passes.

## Rate limit

Each pass accepts **3 google-targeted notifications per rolling 24 hours**, Google Wallet's platform cap on pass messages. Beyond that, creating a notification that targets Google (including the default, which targets both platforms) returns `429` (`PASS_NOTIFICATION_RATE_LIMITED`) with a `Retry-After` header; the cap is re-checked at delivery time for scheduled sends. Apple imposes no such cap: notifications with `"platforms": ["apple"]` are never rate limited.

<Note>
  Wallet-platform behavior differs slightly by design: Apple shows the latest message on the pass's
  back, while Google keeps up to 10 recent messages in the pass's message list. Push delivery timing
  is ultimately up to Apple and Google infrastructure, and holders can disable notifications for a
  pass on their device.
</Note>
