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 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
deviceIdyou choose — a stable per-install key (vendor id, Android ID, or a generated UUID you persist). Re-registering the samedeviceIdupdates the device instead of creating a duplicate; a token that arrives with a differentdeviceId(reinstall, device transfer) is moved to that device. subscriberIdmakes 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 likeno_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: nullrather than fabricating a number. See Verify Delivery.
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.
Next steps
- Device Registration — the register/deregister contract in full.
- Send Notifications — audiences, options and the response shape.
- Framework Recipes — Flutter, bare React Native, Swift, Kotlin, Expo.
- Push Credentials and Verify Delivery.