# Universal Push (Any Framework)

Push notifications for ANY app framework — Flutter, bare React Native, native Swift, native Kotlin, .NET, Unity — by registering APNs and FCM tokens directly instead of Expo tokens.

Native Notify started as an Expo push notification service, and the [Expo SDK path](/docs/setup) is still the fastest setup for Expo apps. **Universal push** is the framework-agnostic path: your app registers its own **APNs** (iOS) and **FCM** (Android) device tokens, and Native Notify sends through Apple's and Google's push services directly.

Any client that can make an HTTPS request can use it — **Flutter, bare React Native, native Swift, native Kotlin, .NET, Unity**, or a backend. No SDK is required.

|                           | Expo SDK path                                                  | Universal push                                         |
| ------------------------- | -------------------------------------------------------------- | ------------------------------------------------------ |
| Token type                | Expo push tokens (`ExponentPushToken[...]`)                    | APNs device tokens + FCM registration tokens           |
| Registration              | `registerNNPushToken()` / SDK functions                        | `POST /api/universal/device/register`                  |
| Who talks to Apple/Google | Expo's push service                                            | Native Notify, with **your** FCM project + Apple key   |
| Delivery evidence         | Expo delivery receipts (\~15 min after a send)                 | APNs/FCM accept-or-reject at send time — no receipts   |
| Extra features            | Notification Inboxes, follow/topic pushes, analytics app-opens | Device/subscriber audiences, health + test-send probes |

You can use both paths in one account — different apps, or even the same app.

## Endpoints

All endpoints live on `https://app.nativenotify.com` and authenticate with your **app id + app token** (the pair on your app in the dashboard), exactly like the rest of the API.

| Endpoint                                     | Auth                      | What it does                                                                |
| -------------------------------------------- | ------------------------- | --------------------------------------------------------------------------- |
| `POST /api/universal/device/register`        | body `appId` + `appToken` | Register a device + its APNs/FCM token(s). Idempotent upsert.               |
| `POST /api/universal/device/deregister`      | body `appId` + `appToken` | Remove a device (logout / uninstall flow). Idempotent.                      |
| `POST /api/universal/notifications/send`     | body `appId` + `appToken` | Send one notification to all devices, a device list, or a subscriber list.  |
| `POST /api/universal/test-send`              | body `appId` + `appToken` | Send to ONE device or token and get the honest result, with a masked token. |
| `GET /api/universal/health/:appId/:appToken` | url pair                  | Device + token counts (live/dead) from the universal service's own storage. |
| `POST /api/universal/credentials/validate`   | session (admin)           | Live check of the app's stored FCM service account + APNs key.              |

## The model

- **A device is identified by a `deviceId` you choose** — a stable per-install key (vendor id, Android ID, or a generated UUID you persist). Re-registering the same `deviceId` updates the device instead of creating a duplicate; a token that arrives with a different `deviceId` (reinstall, device transfer) is moved to that device.
- **`subscriberId` makes a device an indie device.** Send the subscriber id when your app has logged-in users; omit it for a mass (all-users) device. One subscriber can have many devices — that is a supported pattern.
- **APNs tokens belong to iOS devices, FCM tokens to Android devices.** A platform mismatch is rejected with a 400 (`token_platform_mismatch`); there is exactly ever one row per `(app, token type, token)`.
- **Errors are machine-readable**: `{ "error": { "code", "message", "field"? } }` — stable codes like `no_tokens_provided`, `invalid_token_format`, `app_not_found`.
- **No receipts on this path.** APNs and FCM answer a send immediately, and that acceptance is the strongest signal a native token can give — responses say `delivered: null` rather than fabricating a number. See [Verify Delivery](/docs/universal-push/verification).

> **Credentials required:**
>
> Universal sends use **your** push credentials: an FCM v1 service-account JSON (Android) and an Apple APNs `.p8` key (iOS). The legacy FCM server key is no longer accepted — Google shut it down. See [Push Credentials](/docs/universal-push/credentials).

## Next steps

- [Device Registration](/docs/universal-push/registration) — the register/deregister contract in full.
- [Send Notifications](/docs/universal-push/sending) — audiences, options and the response shape.
- [Framework Recipes](/docs/universal-push/recipes) — Flutter, bare React Native, Swift, Kotlin, Expo.
- [Push Credentials](/docs/universal-push/credentials) and [Verify Delivery](/docs/universal-push/verification).
