---
title: "Send Notification"
method: POST
path: "/v2/projects/{id}/notifications"
tags: ["Notifications"]
---

# Send Notification

`POST /v2/projects/{id}/notifications`

## Request

- Base URL: `https://api.superwall.com`
- URL: `https://api.superwall.com/v2/projects/{id}/notifications`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `accounts_superwall_token`
  - HTTP bearer

Sends a notification to one user's devices (`target.app_user_id`, with `devices: "all"` or `"latest"`) or to one device (`target.device_id`), across every application of the project whose notifications are enabled. Delivery is through APNs with the application's APNs key.

The response is 202 `{ "id", "status": "queued" }` once the send is accepted. Superwall then resolves the target to its devices and delivers to each one the user allows notifications on (a background notification also reaches devices whose permission is off). Each device's outcome arrives later as a `NOTIFICATION_SENT` or `NOTIFICATION_FAILED` webhook and a `notification_send` / `notification_fail` event, all carrying this send's `id`. A target with no registered device is accepted and delivers nothing.

Send `notification` content (title, subtitle, body, badge or sound), `content_available: true` for a background notification, or both. A background notification has no visible content, always uses `normal` priority, and reaches devices whose notification permission is off. The encoded APNs payload (including `data` and Superwall's `sw` key) must be at most 4096 bytes.

`idempotency_key` is required: the same key within 30 days returns the original response and sends nothing; the same key with a different body answers 409 `idempotency_key_in_use`, as does a retry while the first request is still being processed. After a 503, retry with the same key and body: the retry sends under the same `id`, and a device that already received the notification may receive it again.

Answers 404 when no application of the project has notifications enabled, 409 `credentials_missing` when none of those applications has an APNs key (an application without one is left out of the send), and 400 for a placeholder `app_user_id` (`null`, `undefined`, `0`, ...), reserved `data` keys (`aps`, `sw`, `deep_link`), or a payload over 4096 bytes.

Requires notifications:send scope.

## Path parameters

- `id` string, required

## Request body

- NotificationSendRequest
  - `target` union, required
    - NotificationUserTarget
      - `app_user_id` string, required: The user, as identified in the Superwall SDK (`Superwall.identify`). Resolved across every application of the project. Placeholder values such as `null`, `undefined`, `unknown` or `0` are rejected with 400.
      - `devices` 'all' | 'latest', required: `all`: every device of the user. `latest`: only the device seen most recently that can receive the notification.
    - NotificationDeviceTarget
      - `device_id` string, required: The SDK's device id. Every registration of that device in the project's applications is targeted.
  - `channel` 'push', required: How to deliver it. Only `push` today: APNs for the project's iOS applications and FCM for its Android applications, chosen per application by the credentials it has (an APNs key, a Firebase service account). One send reaches every platform the target's devices are on.
  - `notification` NotificationContent
    - `title` string: iOS: `aps.alert.title`. Android: `notification.title`.
    - `subtitle` string: iOS only (`aps.alert.subtitle`); Android notifications have no subtitle.
    - `body` string: iOS: `aps.alert.body`. Android: `notification.body`.
    - `badge` integer: iOS: the app icon badge count (`aps.badge`); `0` clears it. Android: `android.notification.notification_count`, shown by launchers that support badges; `0` sets no count, since FCM treats it as unset. Dropped on Android when there is no title or body.
    - `sound` string: `default`, or the name of a sound file in the app bundle (iOS) or in `res/raw` (Android, `android.notification.sound`). Dropped on Android when there is no title or body.
    - `deep_link` string: Sent as `deep_link`: at the top level of the APNs payload on iOS, as a `data` key on Android. Your app opens it when the user taps the notification: the Superwall SDK reports the open but does not open the link.
  - `data` SuperwallApiSchemaV2JsonObject1: Custom keys. iOS: placed at the top level of the APNs payload. Android: sent as FCM `data`, whose values are strings, so a non-string value is JSON-encoded (`3` arrives as `"3"`, `{"a":1}` as its JSON text). Reserved, and answered with 400: `aps`, `sw` and `deep_link` (Superwall's), and FCM's `from`, `notification`, `message_type` and every key starting `google.` or `gcm.`. The reserved keys apply to every send, including a project with only iOS applications: one send can reach both platforms.
  - `ttl_seconds` integer: How long APNs or FCM keeps trying to deliver to a device that is offline, in seconds (iOS: `apns-expiration`; Android: `android.ttl`). Default 86400, at most 2592000. On Android FCM keeps a message at most 28 days (2,419,200 seconds), so a longer value is capped to that. `0` delivers only if the device is reachable now.
  - `priority` 'high' | 'normal': `high` delivers immediately (iOS `apns-priority: 10`, Android `android.priority: HIGH`), `normal` lets the device save power (`5`, `NORMAL`). Defaults to `high`, or `normal` for a background notification, which must be `normal`.
  - `collapse_id` string: A newer notification with the same id replaces this one on the device (iOS `apns-collapse-id`, Android `android.collapse_key`). At most 64 bytes. FCM keeps at most 4 different collapse keys per device at a time, so use only a few distinct values.
  - `thread_id` string: iOS only: groups notifications on the device (`thread-id`).
  - `interruption_level` 'passive' | 'active' | 'time-sensitive': iOS only (`interruption-level`).
  - `content_available` boolean: Wakes the app in the background (iOS `content-available`). With no title, subtitle, body, badge or sound it is a background notification, which reaches devices whose notification permission is off. On Android a notification with no title or body is always a data-only message, delivered to the app without showing anything.
  - `apns` SuperwallApiSchemaV2JsonObject2: iOS only: raw `aps` keys, merged over the ones built from the fields above. Use `content_available`, not `content-available`.
  - `android` SuperwallApiSchemaV2JsonObject3: Android only: raw FCM `AndroidConfig` keys (snake_case), merged over the ones built from the fields above. A key replaces the built one whole: a raw `notification` replaces the built `sound` and `notification_count`. Refused with 400: `data`, `ttl`, `priority`, `collapse_key`, `collapseKey`, which Superwall sets from `data`, `ttl_seconds`, `priority` and `collapse_id` (an `android.data` would replace the message's data, and with it the key Superwall tracks opens by). A raw `notification` does not count as content: a send still needs a title, subtitle, body, badge or sound, or `content_available: true` (which makes it a background notification on iOS). `notification.click_action` opens a different activity when the user taps the notification; the app must call `Superwall.handleNotificationOpen(intent)` from whichever activity handles the tap, or Superwall never sees the open.
  - `idempotency_key` string, required: Your unique key for this send. Sending the same key again within 30 days returns the original response without sending again; the same key with a different body answers 409.

## Response `202`

NotificationSendResponse

- NotificationSendResponse
  - `id` string, required: The send's id (`nsend_` and 26 characters), reported back in the `sw.sendId` key of the payload and in every event and webhook about this send.
  - `status` 'queued', required: `queued`: the send was accepted and will be delivered to the target's devices. Each device's outcome is reported later as a `notification_send` / `notification_fail` event and a `NOTIFICATION_SENT` / `NOTIFICATION_FAILED` webhook.

## Other responses

- `400`: The request did not match the expected schema | The request was invalid or malformed
- `401`: No API key was provided in the request | The API key does not have a valid format | The API key is invalid or has been revoked | An error occurred while validating the API key | The session cookie is invalid or expired
- `403`: The API key does not have permission to perform this action
- `404`: The requested resource was not found
- `409`: The resource already exists or conflicts with an existing resource
- `429`: Too many requests have been made in a short period
- `500`: An unexpected error occurred on the server
- `503`: A required service or security control could not be verified

## Changes

- **2026-11-02** `523b4734dfab`: 1 info
  - added the new optional request property `android`
- **2026-10-29** `778a11cd2c15`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/superwall/apis/superwall-api-v2/changes/v2/projects/:id/notifications/post.md)

---

[API](https://skmtc.dev/superwall/apis/superwall-api-v2.md) - [All operations](https://skmtc.dev/superwall/apis/superwall-api-v2/llms.txt) - [OpenAPI document](https://skmtc.dev/superwall/apis/superwall-api-v2/revisions/523b4734dfab?raw)
