ZeepNotti Docs
SDKs

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 minSdkVersion compatible with com.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-notti

This 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.json at android/app/google-services.json, apply the Google Services Gradle plugin, and declare the POST_NOTIFICATIONS runtime permission in your AndroidManifest.xml (Android). Enable the Push Notifications capability in Xcode and forward the APNs callbacks from your own AppDelegate to ZeepNottiBridge/ZeepNottiPushDelegate (iOS) - the SDK deliberately avoids method swizzling, so this small amount of integrator code is required.
  • Expo: add "react-native-notti" to your app.json/app.config.js plugins array. On expo prebuild, the plugin applies the Google Services Gradle plugin and copies your configured google-services.json (Android), and adds the aps-environment entitlement (iOS). The Android runtime-permission declaration and the iOS AppDelegate forwarding 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.

On this page