React Native SDK
Install, initialize, register devices, and handle notification events.
Overview
react-native-notti is the official React Native SDK for ZeepNotti push
notifications (FCM on Android, APNs on iOS). It handles device
registration, tags, external user id, subscription state, and
notification-received/clicked events, so an integration never has to
hand-roll REST calls against the Devices API
directly. It's a Turbo Module (New Architecture) with a thin TypeScript
facade over native Kotlin/Swift, and ships an Expo config plugin for
projects that use Expo prebuild.
The SDK never hardcodes a host - every app passes ZeepNotti' baseUrl to
initialize().
Requirements
- React Native with the New Architecture enabled (Turbo Modules).
- Android: a
minSdkVersioncompatible withcom.google.firebase:firebase-messaging, and a Firebase project with Cloud Messaging configured. - iOS: the Push Notifications capability enabled on your app target (APNs).
- A ZeepNotti App and its
appId/clientKey- see Get Started with ZeepNotti to create one.
Installation
npm install react-native-nottiThis installs the Turbo Module for both bare React Native and Expo. Native setup differs by path:
- Bare React Native: place your App's
google-services.jsonatandroid/app/google-services.json, apply the Google Services Gradle plugin, and declare thePOST_NOTIFICATIONSruntime permission in yourAndroidManifest.xml(Android). Enable the Push Notifications capability in Xcode and forward the APNs callbacks from your ownAppDelegatetoZeepNottiBridge/ZeepNottiPushDelegate(iOS) - the SDK deliberately avoids method swizzling, so this small amount of integrator code is required. - Expo: add
"react-native-notti"to yourapp.json/app.config.jsplugins array. Onexpo prebuild, the plugin applies the Google Services Gradle plugin and copies your configuredgoogle-services.json(Android), and adds theaps-environmententitlement (iOS). The Android runtime-permission declaration and the iOSAppDelegateforwarding above still apply the same way - the plugin only handles native project configuration, not that code path.
Initializing and registering a device
Call initialize() once, e.g. at app startup, with your App's
appId, clientKey (never the REST key - see the Apps API Reference for
why), and ZeepNotti' baseUrl:
import { ZeepNotti } from 'react-native-notti';
ZeepNotti.initialize('<appId>', '<clientKey>', 'https://api.zeepnotti.example');This registers the device with ZeepNotti using the current FCM (Android) /
APNs (iOS) token. It's safe to call more than once - a repeat call with
the same appId/clientKey is a no-op - and it never throws: missing
or invalid arguments, or a missing native push prerequisite (no
google-services.json, no APNs capability), are logged, not thrown.
Ask for the OS push permission explicitly, whenever your app is ready to
show the prompt - it isn't tied to initialize():
const granted = await ZeepNotti.requestPermission();Tags, external user id, and subscription state
// Tag the device for ZeepNotti Segments (see /api-reference/segments).
ZeepNotti.User.addTag('plan', 'vip');
ZeepNotti.User.addTags({ plan: 'vip', region: 'br' });
ZeepNotti.User.removeTag('plan');
// Associate the device with your own user id.
ZeepNotti.login('external-user-123');
ZeepNotti.logout();
// Enable/disable delivery without unregistering the device.
ZeepNotti.setSubscription(true);All of these mutations are serialized client-side (one in-flight
network call at a time, last-write-wins on the merged local state), so
calling them back-to-back is safe. Note that logout() only clears the
external user id locally - ZeepNotti' backend has no endpoint to clear
external_user_id server-side, so the Device row keeps the previously
set value.
Handling notification events
const received = ZeepNotti.addEventListener('notificationReceived', (payload) => {
console.log(payload.title, payload.body, payload.data);
});
const clicked = ZeepNotti.addEventListener('notificationClicked', (payload) => {
console.log(payload.title, payload.body, payload.data);
});
// Call .remove() when done listening, e.g. in a useEffect cleanup.
received.remove();
clicked.remove();'notificationReceived' fires in the foreground; 'notificationClicked'
fires whenever the app is already running (backgrounded or
foregrounded) and the user taps the notification. Neither fires for a
cold-start tap - the tap that launches the app happens before any
listener can be registered. Check for that case once at startup instead:
ZeepNotti.getInitialNotificationClick().then((payload) => {
if (payload) {
console.log('app was launched by a notification tap', payload);
}
});This resolves the notification that cold-launched the app, or null if
the app wasn't launched that way, and only resolves once per cold start
- the native side clears it after this call reads it.
Without APNs forwarding
If the iOS AppDelegate forwarding step above is skipped,
initialize() still registers the device - with a subscribed: false
state - but push delivery won't complete until the callbacks are wired.