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-nativeConfigure
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 diagnosticsrequestPermission() 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.
Related pages
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.