Quickstart
Register an APNs, FCM or browser push token with the Native Notify API — Swift, Kotlin, Jetpack Compose, React Native, Expo, Flutter and web examples — then send your first push notification.
Register a device and send yourself a push notification. About 10 minutes on web or Android; the first iOS setup adds Apple-console time. Everything is plain HTTPS — no SDK.
1. Create your app
About 2 minutes. Create an app in the dashboard, then open App Settings → App keys. Every request carries that app's app id and app token — in the body for POSTs, in the URL for GETs.
2. Save your push credentials
About 5 minutes with the keys in hand — web push needs none, so web-only setups can skip straight to step 3.
Real sends go out with your push credentials: an FCM v1 service-account JSON for Android and an Apple APNs .p8 key (with its Key ID, your Team ID and the app's bundle id) for iOS. Save them under App Settings → Push credentials. Each save is verified live against Apple and Google. Where to get each one: Push Credentials.
3. Register a device
About 5 minutes — this is the step your app runs at launch.
POST https://app.nativenotify.com/api/universal/device/register
{
"appId": 123,
"appToken": "yourAppToken",
"deviceId": "A1B2C3D4-install-key",
"platform": "ios",
"tokens": { "apnsToken": "0c9d8e…" }
}
Registration stores two things for the device: the device id you generate once and keep, and the platform's push token — APNs on iOS, FCM on Android, a PushSubscription on the web. The tabs above show the exact platform and tokens shape for each. Registration is an idempotent upsert — call it on every app launch and whenever the token changes.
Examples
Ask for permission, let APNs hand you the device token, and register it:
import UIKit
import UserNotifications
@main
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in
guard granted else { return }
DispatchQueue.main.async { application.registerForRemoteNotifications() }
}
return true
}
// APNs delivers the device token here.
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let apnsToken = deviceToken.map { String(format: "%02x", $0) }.joined()
Task { await register(apnsToken: apnsToken) }
}
func register(apnsToken: String) async {
var request = URLRequest(url: URL(string: "https://app.nativenotify.com/api/universal/device/register")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: [
"appId": 123,
"appToken": "yourAppToken",
"deviceId": DeviceIdentity.current, // a stable id you persist (Keychain, UserDefaults)
"platform": "ios",
"tokens": ["apnsToken": apnsToken]
])
_ = try? await URLSession.shared.data(for: request) // 201 = registered
}
}
The full Swift walkthrough — including the stable deviceId and tap handling — is the Swift recipe.
4. Send yourself a test push
About 1 minute. Send to the device you just registered and read the honest per-transport result:
curl -X POST https://app.nativenotify.com/api/universal/test-send \
-H "Content-Type: application/json" \
-d '{
"appId": 123,
"appToken": "yourAppToken",
"title": "Hello from Native Notify",
"message": "If you can read this, APNs/FCM accepted it.",
"deviceId": "YOUR_DEVICE_ID"
}'
5. Send to everyone
About 1 minute. Reach every registered device — change the audience:
curl -X POST https://app.nativenotify.com/api/universal/notifications/send \
-H "Content-Type: application/json" \
-d '{
"appId": 123,
"appToken": "yourAppToken",
"title": "Hello everyone",
"message": "Our first push notification.",
"audience": { "type": "all" }
}'
Stuck?
| Symptom | Likely cause |
|---|---|
401, invalid app id or token | The appToken is wrong — or a publishable web key (nnweb_…) was used from a native device. The web key belongs only in the appToken field of a platform: "web" registration. |
400 invalid_token_format | The token isn't the raw APNs or FCM string. Expo push tokens are rejected by this API — pass the native token (getDevicePushTokenAsync()); see the Expo recipe. |
400 token_platform_mismatch | platform and the token field disagree: apnsToken → ios, fcmToken → android, webPush → web. |
test-send refused with a reasonCode | The transport rejected it — nearly always the credential from step 2 (bundle id, team id, or a sandbox/production mix-up). Verify Delivery reads every reason. |
| Accepted, but nothing shows on Android | FCM delivers data messages, so your app displays them itself — see the display section of your platform guide: Kotlin, Jetpack Compose, Flutter. |
| Accepted, but nothing shows on iPhone or iPad (web) | Safari delivers web push only to sites added to the Home Screen first. |
403 plan_upgrade_required | The plan doesn't include that audience — Premium allows one subscriber or "all", not groups or device lists. See Plans & Billing. |
Next steps
- Device Registration — every field, response and error code.
- Send Notifications — audiences, rich fields and the send report.
- Verify Delivery — test sends, device counts and credential checks.
- Framework Recipes — Swift, Kotlin, Jetpack Compose, Expo, bare React Native and Flutter walkthroughs.
- Notification inbox — read and manage each device's inbox, with unread counts.
- Analytics — active users, screens, sessions and push opens.