Carillon docs

React Native SDK

Set up Expo or a bare React Native app for registration and notification taps.

For text, number, boolean and date attributes, SDK methods and audience operators, see Tags.

@exostack/carillon-react-native requires the React Native New Architecture. It supports Expo through a config plugin and bare apps through native callback forwarding.

Samples on this page show placeholders. Sign in to the dashboard in this browser and they fill in with your own organization, app and mobile key.

Install

yarn add @exostack/carillon-react-native

Configure

import Carillon from '@exostack/carillon-react-native'Carillon.configure({ key: 'carillon_mk_live_your_mobile_key', debug: __DEV__ })// Call when your app should request notification permission.// 'allowed' | 'denied' | 'provisional' | 'undetermined'const permission = await Carillon.requestPermission()Carillon.identify('user-42')          // null forgets the identifierCarillon.setTag('plan', 'pro');Carillon.setTagNumber('seats', 12);Carillon.setTag('language', 'fr');Carillon.removeTagNumber('seats');Carillon.removeTag('language');Carillon.optOut()Carillon.optIn()const off = Carillon.onOpened((notification) => {const orderId = notification.payload.order_idif (typeof orderId === 'string') {  // Open this order in your app.}})await Carillon.debugInfo()            // registration diagnostics

requestPermission() takes nothing on either platform. Android's native call needs the activity its dialogue belongs to, and the module supplies the current one. With no activity in the foreground the promise rejects, since there is no screen to show a prompt on.

Expo

The config plugin ships inside this package.

In Expo Go, the SDK stays inactive and logs one warning when imported. It does not register a device, send tag updates or emit notification events. Permission methods return undetermined, canRequestPermission() returns false, and getDeviceId() returns null. debugInfo() reports available: false and reason: 'expo_go'. Tag argument validation still applies. Use a development build to test notifications.

{
  "expo": {
    "plugins": ["@exostack/carillon-react-native"],
    "android": { "googleServicesFile": "./google-services.json" }
  }
}

Run expo prebuild, then rebuild the native app. The plugin configures iOS push entitlements and callbacks, the Android messaging service, and Google services when the JSON file is present. For Kotlin MainActivity, it also forwards cold-start and warm notification taps from onCreate and onNewIntent. Existing callbacks are preserved. Java activities require conversion to Kotlin for automatic configuration; bare apps can use the manual forwarding below. The SDK detects the APNs environment from the signed build.

Native availability and OTA updates

With React Native SDK 0.4.3 and later, Carillon.isAvailable() reports whether the binary includes the native module. Expo Go, web and missing-module runtimes are inactive, so a static import does not require an application-side web stub. debugInfo() explains the inactive runtime. An older binary cannot gain native push support through an OTA JavaScript update.

We recommend runtimeVersion: { policy: 'fingerprint' } when using Expo updates. Rebuild after adding or changing native dependencies. A policy based only on the Expo SDK version does not track native module additions between SDK upgrades. See Expo runtime compatibility.

Local iOS signing

For push notifications on a physical iPhone, use a team enrolled in the Apple Developer Program. A free Personal Team cannot provision the Push Notifications capability; removing the entitlement only hides the signing error and disables the integration you are trying to test.

Set your team explicitly before generating the project:

{
  "expo": {
    "ios": {
      "bundleIdentifier": "com.example.app",
      "appleTeamId": "YOURTEAMID"
    },
    "plugins": ["@exostack/carillon-react-native"]
  }
}

Run npx expo prebuild -p ios, then open the generated .xcworkspace in Xcode. In Signing & Capabilities, choose the same team and enable automatic signing for the app and for CarillonNotificationExtension. The extension is a separate target with its own bundle identifier and provisioning profile. Build once for the connected device to create or refresh both profiles, then use npx expo run:ios --device.

For a direct xcodebuild invocation, -allowProvisioningUpdates allows Xcode to create or update signing assets when your account has permission. It is an xcodebuild option, not an Expo CLI flag, and does not give a Personal Team the push capability. See Expo's Xcode signing guide and Apple's signing workflow.

Bare workflow

Add the push capability, make your app delegate conform to UNUserNotificationCenterDelegate, then forward the registration and tap callbacks below. Also forward willPresent as shown under Foreground notifications. Import CarillonReactNative, not Carillon: your app links this package, and the Swift SDK is resolved for this package's pod alone.

import CarillonReactNative

func application(
  _ application: UIApplication,
  didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
  CarillonBridge.didRegister(token: deviceToken)
}

func application(
  _ application: UIApplication,
  didFailToRegisterForRemoteNotificationsWithError error: Error
) {
  CarillonBridge.didFailToRegister(error)
}

func userNotificationCenter(
  _ center: UNUserNotificationCenter,
  didReceive response: UNNotificationResponse,
  withCompletionHandler completionHandler: @escaping () -> Void
) {
  CarillonBridge.didOpen(response)
  completionHandler()
}

Set UNUserNotificationCenter.current().delegate before anything else in didFinishLaunching.

Opened payloads

onOpened exposes the reserved payload.carillon field as an object on both platforms. The bridge parses the Android JSON stamp without changing customer payload fields. deliveryId is also available directly on the notification.

setTag and setTags accept strings. Use setTagNumber for finite numbers, setTagBoolean for booleans and setTagDate for valid JavaScript dates. Invalid values are rejected before reaching the native bridge. See tags.

Track opens · SDK overview · Troubleshooting

Foreground notifications

const unsubscribe = Carillon.onReceived(async (notification) => {
  // Read notification.data; return 'suppress' for your own in-app UI.
  return 'show'
})

Carillon.clearNotifications()

A new onReceived call replaces the previous handler. Unsubscribe when the handler is no longer needed. The native SDK shows the notification if JavaScript does not answer within three seconds. A thrown or rejected handler also falls back to showing it. The listener attaches before native observation starts.

The Expo plugin forwards iOS willPresent. In a bare app, forward it explicitly:

func userNotificationCenter(_ center: UNUserNotificationCenter,
  willPresent notification: UNNotification,
  withCompletionHandler completion: @escaping (UNNotificationPresentationOptions) -> Void) {
  CarillonBridge.willPresent(notification, completionHandler: completion)
}

Use override when your AppDelegate superclass implements this method. Keep the notification center delegate installed at launch. Android's foreground renderer uses the native notification permission and channels.

Images on iOS

The Expo plugin creates a CarillonNotificationExtension target and declares it under extra.eas.build.experimental.ios.appExtensions, with bundle identifier <ios.bundleIdentifier>.CarillonNotificationExtension. Set ios.bundleIdentifier in your Expo configuration. EAS needs a provisioning profile for this target; no App Group is required. Run prebuild again, then rebuild the native app. Expo Go cannot load this native extension.

For a bare app, follow the Swift extension setup. When migrating from OneSignal, replace its notification service extension instead of keeping two service extensions.

Device ID

const deviceId = await Carillon.getDeviceId() // null before registration
const stopObserving = Carillon.onDeviceIdChanged((id) => {
  // Update a stored equipment-to-device mapping if needed.
})

The ID survives token rotation when the native installation proof is available. It is a routing identifier, not a login credential. See device identity.

Coexistence with another notification library

A library's click events may cover only notifications from its own service. Do not assume that its JavaScript hooks receive Carillon notifications. On iOS, an app-owned or existing notification-center delegate must explicitly forward taps to CarillonBridge.didOpen and foreground presentation to CarillonBridge.willPresent. Passing the completion handler to Carillon transfers responsibility for calling it; do not call it again. The foreground answer may be asynchronous, with a three-second fallback to showing.

Keep only one owner of Android's FirebaseMessagingService. A broadcast receiver alone is not such a service. The Expo plugin's messagingService: "auto" detects services, not receivers. Use "external" only when the existing service forwards messages and tokens. Set installNotificationCenterDelegate: false only when an existing iOS delegate forwards the native callbacks. Disabling another SDK's interception may disable its own handling.

Test foreground show/suppress, background taps, cold-start taps and token rotation for both senders. See the migration guide for audience handover.

Android background appearance

Messages with a notification payload are displayed by Firebase while the app is in the background. They bypass CarillonMessagingService and its display settings. Foreground Carillon rendering uses carillon_default and the drawable carillon_notification_icon; Firebase needs its own defaults.

Add a monochrome notification drawable named carillon_notification_icon, then put these entries inside <application> in AndroidManifest.xml:

<meta-data
  android:name="com.google.firebase.messaging.default_notification_icon"
  android:resource="@drawable/carillon_notification_icon" />
<meta-data
  android:name="com.google.firebase.messaging.default_notification_channel_id"
  android:value="carillon_default" />

Create the channel at startup, before the first background notification. For example, add this to your application's onCreate, after super.onCreate():

if (android.os.Build.VERSION.SDK_INT >= 26) {
  getSystemService(android.app.NotificationManager::class.java)
    .createNotificationChannel(android.app.NotificationChannel(
      "carillon_default", "Notifications", android.app.NotificationManager.IMPORTANCE_DEFAULT
    ))
}

Use a localized channel name in your app. If you choose a different channel ID, create it before use and configure the Firebase default accordingly. Existing channels keep the user's settings.

Without these defaults Firebase may use its fallback channel and the app icon. Changing to data-only messages changes background processing and delivery behavior; it is not required to configure the icon and channel. See Firebase message handling.

Receipt tracking

React Native SDK 0.5.0 uses native receipt tracking. No JavaScript subscription is needed. It records receive callbacks and checks notifications still present when the app starts or resumes. See receipt tracking for the distinction between receipt, display and opening.

To capture receipts in the iOS notification service extension, configure a shared App Group with the Expo plugin:

["@exostack/carillon-react-native", { "appGroup": "group.com.example.app.carillon" }]

Enable the group for both app and extension in Apple Developer and rebuild with matching provisioning profiles. The plugin sets the entitlements and CarillonAppGroup Info.plist value on both targets. The host app reports stored receipts at its next launch or activation. For bare iOS apps, configure both targets as described in the Swift guide.

On this page