# React Native SDK Documentation Generated on: 1/30/2026 This documentation bundle contains the complete React Native SDK documentation for use with LLMs and offline reference. --- ## Quick start guide **Source:** /integrations/sdk/react-native/quick-start-guide # Quick Start Guide  Our MCP server can help you get started Our MCP server includes SDK-installation tools that can help you get integrated quickly with Customer.io and troubleshoot any issues you might have. See [Set up an MCP server](/ai/mcp-server/) to get started. ## Setup process overview[](#setup-process-overview) React Native lets you build native mobile apps with JavaScript. Our React Native SDK helps you integrate Customer.io to `identify` people, `track` their activity, and send both push notifications and in-app messages. 1. [Install the SDK.](#install) 2. [Identify and Track](#identify-and-track) 3. [Push Notifications](#push-notifications) 4. [In-App](#in-app) ## 1\. Install the SDK[](#install) You need to add a new *React Native* connectionRepresents an integration between Customer.io and another service or app under *[Data & Integrations > Integrations](https://fly.customer.io/workspaces/last/integrations/all/overview)*. A connection in Customer.io provides you with API keys and settings for your integration. in Customer.io to get your CDP API key. See [Get your CDP API key](../getting-started/auth/#get-your-cdp-api-key) for details. 1. Make sure you [set up your React Native environment](https://reactnative.dev/docs/environment-setup) first. **You must use React Native 0.79 or later**. 2. Open your terminal and go to your project folder. 3. Install the `customerio-reactnative` package using NPM or Yarn: ```bash npm install customerio-reactnative # or yarn add customerio-reactnative ``` 4. Set up your project to support iOS and/or Android deployments: iOS #### iOS[](#iOS) For iOS, install CocoaPods dependencies: ```bash pod install --repo-update --project-directory=ios ``` Make sure your minimum iOS deployment target is set to `13.0` in both your `Podfile` and Xcode project settings. Android #### Android[](#Android) 1. For Android, include the Google Services plugin by adding the following to your project-level `android/build.gradle` file: ```groovy buildscript { repositories { google() // Google's Maven repository } dependencies { classpath 'com.google.gms:google-services:' // Google Services plugin } } allprojects { repositories { google() // Google's Maven repository } } ``` 2. Add the plugin to your app-level `android/app/build.gradle`: ```groovy apply plugin: 'com.google.gms.google-services' // Google Services plugin ``` 3. Download `google-services.json` from your Firebase project and place it in `android/app/google-services.json`. 5. Add your *CDP API key* and *site ID* to your configuration. * **CDP API Key**: You’ll find this key in your [*React Native* connection](https://fly.customer.io/workspaces/last/pipelines/sources). * **Site ID**: You’ll find this value in your workspace under **[Settings > Workspace Settings > API and webhook credentials](https://fly.customer.io/workspaces/last/settings/api_credentials)**. 6. Initialize the SDK in your app. Add the following code to your main component or App.js file: ```typescript import React, { useEffect } from 'react'; import { CioConfig, CioRegion, CioLogLevel, CustomerIO, PushClickBehaviorAndroid } from 'customerio-reactnative'; useEffect(() => { const initializeCustomerIO = async () => { const config: CioConfig = { cdpApiKey: 'YOUR_CDP_API_KEY', // Required region: CioRegion.US, // Replace with CioRegion.EU if your Customer.ioaccount is in the EU logLevel: CioLogLevel.Debug, trackApplicationLifecycleEvents: true, inApp: { siteId: 'YOUR_SITE_ID', // Required for in-app messaging } }; await CustomerIO.initialize(config); }; initializeCustomerIO(); }, []); ``` 7. Run your application to ensure everything is set up correctly: * **iOS**: `npx react-native run-ios` * **Android**: `npx react-native run-android` ## 2\. Identify and Track[](#identify-and-track) 1. Identify a user in your app using the `CustomerIO.identify` method. You *must* identify a user before you can send push notifications and personalized in-app messages. ```typescript import { CustomerIO } from "customerio-reactnative"; const identifyUserExample = async () => { await CustomerIO.identify({ userId: '[email protected]', traits: { firstName: 'John', lastName: 'Doe', email: '[email protected]', subscriptionStatus: 'active', }, }); console.log('User identified successfully'); }; ``` 2. Track a custom event using the `CustomerIO.track` method. Events help you trigger personalized campaigns and track user activity. ```typescript import { CustomerIO } from "customerio-reactnative"; const trackCustomEventExample = async () => { await CustomerIO.track('purchased_item', { product: 'Premium Subscription', price: 99.99, currency: 'USD' }); console.log('Custom event tracked successfully'); }; ``` 3. Track screen views to trigger in-app messages associated with specific screens. ```typescript import { CustomerIO } from "customerio-reactnative"; const trackScreenViewExample = async () => { await CustomerIO.screen('ProductDetails'); console.log('Screen view tracked successfully'); }; ``` ## 3\. Push Notifications[](#push-notifications) 1. Set up your push notification credentials in Customer.io: * **iOS**: [Upload your Apple Push Notification certificate](https://fly.customer.io/workspaces/last/settings/actions/push/ios) (.p8 file). * **Android**: [Upload your Firebase Cloud Messaging server key](https://fly.customer.io/workspaces/last/settings/actions/push/android) (.json format). 2. Request push notification permissions from the user: ```typescript import { CustomerIO, CioPushPermissionStatus } from "customerio-reactnative"; const requestPushPermissions = async () => { const permissionStatus = await CustomerIO.pushMessaging.showPromptForPushNotifications({ ios: { sound: true, badge: true } }); switch (permissionStatus) { case CioPushPermissionStatus.Granted: console.log('Push notifications enabled'); break; case CioPushPermissionStatus.Denied: case CioPushPermissionStatus.NotDetermined: console.log('Push notifications denied'); break; } }; ``` 3. For iOS: to ensure that metrics are tracked, configure Background Modes. In Xcode, enable “Remote notifications” under Capabilities > Background Modes. 4. For Android: add notification icon resources: * Place a notification icon file named `ic_notification.png` in your drawable folders. * Make sure your app’s `AndroidManifest.xml` has the proper FCM permissions. ## 4\. In-App[](#in-app) 1. To enable in-app messaging, all you need to do is add the site ID. Remember, you’ll find your site ID under **[Data & Integrations > Integrations > Customer.io API: Track](https://fly.customer.io/workspaces/last/settings/api_credentials)** in the *Connections* tab. 2. Ensure that the SDK is initialized with the site ID in your app. You can call the `initialize` method from your components or services: ```typescript import { CioConfig, CustomerIO } from "customerio-reactnative"; import { useEffect } from "react"; useEffect(() => { const initializeCustomerIO = async () => { const config: CioConfig = { cdpApiKey: 'YOUR_CDP_API_KEY', inApp: { siteId: 'YOUR_SITE_ID', } }; await CustomerIO.initialize(config); }; initializeCustomerIO(); }, []); ```  Check out our code samples! You’ll find a complete, working sample app in our [React Native SDK’s example directory](https://github.com/customerio/customerio-reactnative/tree/main/example). If you get stuck, you can refer to the sample app to see how everything fits together. --- ## Getting started > Auth **Source:** /integrations/sdk/react-native/getting-started/auth # Authentication To use the SDK, you’ll need two kinds of API keys: A *CDP API Key* to send data to Customer.io and a *Site ID*, telling the SDK which workspace your messages come from. These keys come from different places in Customer.io! 1. **CDP API Key**: You’ll get this key when you [set up your mobile app as a data-in integration](#set-up-a-new-source) in Customer.io. 2. **Site ID**: This key tells the SDK which workspace your in-app messages come from. You’ll use it to support `inApp` messages. If you’re upgrading from a previous version of the Customer.io SDK, it also serves as the `migrationSiteId`. ## Get your CDP API Key[](#get-your-cdp-api-key) You’ll use your write key to initialize the SDK and send data to Customer.io; you’ll get this key from your React Native entry under **[Data & Integrations > Integrations](https://fly.customer.io/workspaces/last/integrations/all/overview)**. If you don’t see your app on this page, you’ll need to [add up a new integration](#set-up-a-new-source). 1. Go to *Data & Integrations* > Integrations\*. 2. Go to the **Connections** tab and find your React Native connection. If you don’t have a React Native connection, you’ll need to [set one up](#set-up-a-new-source). [![the connections page, showing an react native source connected to a journeys destination](https://customer.io/images/cdp-react-native-connected-destination.png)](#0dfe697d9a6725af8493a5709d6adc95-lightbox) 3. Go to **Settings** and find your **API Key**. Copy this key into the `CioConfig.CdpApiKey` config option. [![get your CDP API Key from your connection's settings page](https://customer.io/images/cdp-react-native-source-api-key.png)](#53593c4c304208d983b57468465e6b6a-lightbox) ```javascript import { CioRegion, CustomerIO, CioConfig } from 'customerio-reactnative'; const App = () => { useEffect(() => { const config: CioConfig = { cdpApiKey: 'CDP API Key', // Mandatory migrationSiteId: 'siteId', // Required if migrating from an earlier version region: CioRegion.US, // Replace with CioRegion.EU if your Customer.io account is in the EU. inApp: { siteId: 'site_id', } }; CustomerIO.initialize(config) }, []) } ``` ## Add a new integration[](#set-up-a-new-source) If you don’t already have a write key, you’ll need to set up a new connectionRepresents an integration between Customer.io and another service or app under *[Data & Integrations > Integrations](https://fly.customer.io/workspaces/last/integrations/all/overview)*. A connection in Customer.io provides you with API keys and settings for your integration.. The connection represents your app and the stream of data that you’ll send to Customer.io. 1. Go to [*Data & Integrations* > *Integrations*](https://fly.customer.io/workspaces/last/journeys/integrations/all/overview) and click **Add Integration**. 2. Select **React Native**. 3. Enter a *Name* for your integration, like “My React Native App”. 4. We’ll present you with a code sample containing a `cdpApiKey` that you’ll use to initialize the SDK. Copy this key and keep it handy. 5. Click **Complete Setup** to finish setting up your integration. [![Set your name, get your CDP API Key, and click Complete Setup](https://customer.io/images/cdp-react-native-source-setup.png)](#40e573b250197f1c640f816429d6bf9b-lightbox) Remember, you can also [connect your React Native app to services outside of Customer.io](/integrations/data-out/add-destination/)—like your analytics provider, data warehouse, or CRM. ## Get your Site ID[](#get-your-site-id) You’ll use your site ID with the `inApp` option to support in-app messaging. And if you’re upgrading from a previous version of the SDK, you’ll also use your site ID as your `migrationSiteId`. This key is used to send remaining tasks to Customer.io when your audience updates your app. 1. Go to Settings > **Workspace Settings** in the upper-right corner of the Customer.io app and go to [**API and Webhook Credentials**](https://fly.customer.io/workspaces/last/settings/api_credentials). 2. Copy the **Site ID** for the set of credentials that you want to send your in-app messages from. If you don’t have a set of credentials, click **Create Tracking API Key**. [![find your site ID](https://customer.io/images/cdp-js-site-id.png)](#64d2b27827ffddb00dc77b851a7a6854-lightbox) 3. You’ll use this key to initialize the `inApp` package. ```javascript import { CioLogLevel, CioRegion, CustomerIO, CioConfig } from 'customerio-reactnative'; const App = () => { useEffect(() => { const config: CioConfig = { cdpApiKey: 'CDP API Key', // Mandatory migrationSiteId: 'siteId', // Required if migrating from an earlier version region: CioRegion.US, // Replace with CioRegion.EU if your Customer.io account is in the EU. logLevel: CioLogLevel.Debug, trackApplicationLifecycleEvents: true, inApp: { siteId: 'site_id', }, push: { android: { pushClickBehavior: PushClickBehaviorAndroid.ActivityPreventRestart } } }; CustomerIO.initialize(config) }, []) } ``` ## Securing your credentials[](#securing-your-credentials) To simplify things, code samples in our documentation sometimes show API keys directly in your code. But you don’t have to hard-code your keys in your app. You can use environment variables, management tools that handle secrets, or other methods to keep your keys secure if you’re concerned about security. To be clear, the keys that you’ll use to initialize the SDK don’t provide read access to data in Customer.io; they only write data to Customer.io. A bad actor who found your credentials can’t use your keys to read data from our servers. --- ## Getting started > How it works **Source:** /integrations/sdk/react-native/getting-started/how-it-works # How it works Before you can take advantage of our SDK, you need to install the module(s) you want to use, initialize the SDK, and understand the order of operations. Our SDKs provide a ready-made integration to identify people who use mobile devices and send them notifications. Before you start using the SDK, you should understand a bit about how the SDK works with Customer.io. Before a person logs into your app, any activity they perform is associated with an anonymous person in Customer.io. In this state, you can track their activity, but you can’t send them messages through Customer.io. When someone logs into your app, you’ll send an `identify` call to Customer.io. This makes the person eligible to receive messages and reconciles their anonymous activity to their identified profile in Customer.io. You send messages to a person through the Customer.io campaign builder, broadcasts, etc. These messages are not stored on the device side. If you want to send an event-triggered campaign to a mobile device, the mobile device user must be identified and have a connection such that it can send an event back to Customer.io and receive a message payload. ## Your app is a data source and Customer.io is a destination[](#your-app-is-a-data-source-and-customerio-is-a-destination) Our SDK is a data inAn integration that feeds data *into* Customer.io. integration. It routes data from your app to both Customer.io *and* any other outbound services where you might use your mobile data. This makes it easy to use your app as a part of your larger data stack without using extra packages or code. When you set up your app, you’ll integrate our SDK. But you’ll also determine where you want to route your data to—your Customer.io workspace *and* destinations outside of Customer.io. [![the connections page, showing an android source connected to a journeys destination](https://customer.io/images/cdp-react-native-connected-destination.png)](#0dfe697d9a6725af8493a5709d6adc95-lightbox) ## Minimum requirements[](#minimum-requirements) To support the Customer.io SDK, you must: * Use React Native versions 0.79 and later. Current versions of XCode don’t support earlier versions of React Native. * Set iOS 13 or later as your minimum deployment target in XCode * Have an Android device or emulator with Google Play Services enabled and a minimum OS version between Android 5.0 (API level 21) and Android 13.0 (API level 33). * Have an iOS 13+ device to test your implementation. You cannot test push notifications in a simulator. * Add React Navigation to your app to support deep links and screen tracking. ## The Processing Queue[](#the-processing-queue) The SDK automatically adds all calls to a queue system, and waits to perform these calls until certain criteria is met. This queue makes things easier, both for you and your users: it handles errors and retries for you (even when users lose connectivity), and it can save users’ battery life by batching requests. The queue holds requests until any one of the following criteria is met: * There are 20 or more tasks in the queue. * 30 seconds have passed since the SDK performed its last task. * The app is closed and re-opened. For example, when you identify a new person in your app using the SDK, you won’t see the created/updated person immediately. You’ll have to wait for the SDK to meet any of the criteria above before the SDK sends a request to the Customer.io API. Then, if the request is successful, you’ll see your created/updated person in your workspace. --- ## Getting started > Packages options **Source:** /integrations/sdk/react-native/getting-started/packages-options # Packages and Configuration Options ## SDK packages[](#sdk-packages) The SDK consists of a few packages. You *must* use the `CioConfig` and `CustomerIO` packages to configure and initialize the SDK. Package Product Required? Description CustomerIO ✅ The main SDK package. Used to initialize the SDK and call the SDK’s methods. CioConfig ✅ Configure the SDK including in-app messaging support. CioRegion Used inside the `CioConfig.region` option to declare your region—EU or US. CioLogLevel Used inside the `CioConfig.logLevel` option to set the level of logs you can view from the SDK. ## Configuration options[](#configuration-options) You can determine global behaviors for the SDK in using `CioConfig` package. You must provide configuration options before you initialize the SDK; you cannot declare configuration changes after you initialize the SDK. Import `CioConfig` and then set configuration options to configure things like your logging level and whether or not you want to automatically track device attributes, etc. Note that the `logLevel` option requires the `CioLogLevel` package and the `region` option requires the `CioRegion` package. ```javascript import { CioLogLevel, CioRegion, CustomerIO, CioConfig } from 'customerio-reactnative'; const App = () => { useEffect(() => { const config: CioConfig = { cdpApiKey: 'CDP API Key', // Mandatory migrationSiteId: 'siteId', // Required if migrating from an earlier version region: CioRegion.US, // Replace with CioRegion.EU if your Customer.io account is in the EU. logLevel: CioLogLevel.Debug, trackApplicationLifecycleEvents: true, inApp: { siteId: 'site_id', }, push: { android: { pushClickBehavior: PushClickBehaviorAndroid.ActivityPreventRestart } } }; CustomerIO.initialize(config) }, []) } ``` Option Type Default Description `cdpApiKey` string **Required**: the key you'll use to initialize the SDK and send data to Customer.io `region` `CioRegion.EU` or `CioRegion.US` `CioRegion.US` ****Requires the CioRegion package.** You must set the region your account is in the EU (`CioRegion.EU`).** `apiHost` string The domain you’ll proxy requests through. You’ll only need to set this (and `cdnHost`) if you’re [proxying requests](#proxying-requests). `autoTrackDeviceAttributes` boolean `true` Automatically gathers information about devices, like operating system, device locale, model, app version, etc `cdnHost` string The domain you’ll fetch configuration settings from. You’ll only need to set this (and `apiHost`) if you’re [proxying requests](#proxying-requests). `logLevel` string `error` **Requires the `CioLogLevel` package**. Sets the level of logs you can view from the SDK. Set to `debug` or `info` to see more logging output. `migrationSiteId` string **Required if you're updating from 3.x**: the credential for previous versions of the SDK. This key lets the SDK send remaining tasks to Customer.io when your audience updates your app. `screenViewUse` `All` or `InApp` `All` `ScreenView.All` (Default): Screen events are sent to Customer.io. You can use these events to build segments, trigger campaigns, and target in-app messages. `ScreenView.InApp`: Screen view events not sent to Customer.io. You’ll *only* use them to target in-app messages based on page rules. `trackApplicationLifecycleEvents` boolean `true` Set to `false` if you don't want the app to send lifecycle events `inApp` object **Required for in-app support**. This object takes a `siteId` property, determining the workspace your in-app messages come from. `push` object Takes a single option called `PushClickBehaviorAndroid`. This object and option controls how your app behaves when your Android audience taps push notifications. ## Proxying requests[](#proxying-requests) By default, requests go through our domain at `cdp.customer.io`. You can proxy requests through your own domain to provide a better privacy and security story, especially when submitting your app to app stores. To proxy requests, you’ll need to set the `apiHost` and `cdnHost` properties in your `SDKConfigBuilder`. While these are separate settings, you should set them to the same URL. While you need to initialize the SDK with a `cdpApiKey`, you can set this to any value you want. You only need to pass your actual key when you send requests from your server backend to Customer.io. If you want to secure requests to your proxy server, you can set the `cdpApiKey` to a value representing basic authentication credentials that you handle on your own. See [proxying requests](/integrations/data-in/proxying-requests) for more information. ```javascript import { CustomerIO, CioConfig, CioRegion, } from 'customerio-reactnative'; const config: CioConfig = { cdpApiKey: 'your-cdp-api-key', region: CioRegion.US, inApp: { siteId: 'your-site-id', }, // Proxy requests through your own domain apiHost: 'proxy.example.com', cdnHost: 'proxy.example.com', } as CioConfig; CustomerIO.initialize(config); ``` --- ## Getting started > Troubleshooting **Source:** /integrations/sdk/react-native/getting-started/troubleshooting # Troubleshooting If you’re having trouble with the SDK, here are some basic steps to troubleshoot your problems, and solutions to some known issues. ## Basic troubleshooting steps[](#basic-troubleshooting-steps) 1. **Make sure your app meets our [prerequisites](/integrations/sdk/react-native/getting-started/#prerequisites)**: Attempting to use our SDK in an environment that doesn’t match our supported versions may result in build errors. 1. **Update to the latest version**: When troubleshooting problems with our SDKs, we generally recommend that you try updating to the latest version. That helps us weed out issues that might have been seen in previous versions of the SDK. 2. **Try running our MCP server**: Our MCP server includes an `integration` tool that can provide immediate help with your implementation, including problems with push and in-app notifications. See [Use our MCP server to troubleshoot your implementation](#troubleshoot-with-mcp) below. 3. **Enable `debug` logging**: Reproducing your issue with `loglevel` set to `debug` can help you (or us) pinpoint problems.  Don’t use debug mode in your production app Debug mode is great for helping you find problems as you integrate with Customer.io, but we strongly recommend that you set `loglevel` to `error` in your publicly available, production app. 4. **Try our test image**: Using [an image](#image-display-issues) that we know works in push and in-app notifications can help you narrow down problems relating to images in your messages. ### If you need to contact support[](#if-you-need-to-contact-support) We’re here to help! If you contact us for help with an SDK-related issue, we’ll generally ask for the following information. Having it ready for us can help us solve your problem faster. 1. **Share information about your device and environment**: Let us know where you had an issue—the SDK and version of the SDK that you’re using, the specific device, operating system, message, use case, and so on. The more information you share with us, the easier it is for us to weed out externalities and find a solution. 2. **Provide comprehensive debug logs**: When sharing logs with our support team, please ensure your logs include: * **SDK initialization**: Show that the SDK was initialized with your site ID and API key * **Profile identification**: Show that a profile was identified in your app * **Issue reproduction**: Capture the exact issue you’re experiencing * **Unfiltered logs**: Provide complete, unfiltered logs—don’t remove or filter out any log entries * **Debug level enabled**: Make sure `loglevel` is set to `debug` when capturing logs for support 3. **For push notification issues**: * **Use live push examples**: If your issue relates to push notifications, provide logs from a **live push notification** sent through a campaign or API call, not a test send. Live pushes show the actual payload that was delivered to the profile. * **Test in different app states**: Test and document the issue in various app states: * **Foreground**: App is open and active * **Background**: App is running but not in focus * **Killed/Terminated**: App is completely closed * **Include the push payload**: Share the complete push notification payload that you sent. 4. **[Grant access to your workspace](/journeys/privacy/#support-team-access)**: It may help us to see exactly what triggers a campaign, what data is associated with devices you’re troubleshooting, etc. You can grant access for a limited time, and revoke access at any time. ### Troubleshooting issues with our MCP server[](#troubleshoot-with-mcp) Our [MCP server](/ai/mcp-server/) includes an `integration` tool that can help troubleshoot your implementation, including problems with push and in-app notifications. It has a deep understanding of our SDKs and provides an immediate way to get support with your implementation—without necessarily needing to capture debug logs, etc. You can ask the MCP server basic questions like, “My push notifications aren’t working. Can you help me troubleshoot the problem?” Or you can ask more specific questions like, “Deep links in push notifications don’t work for customers in my Android app.” Or “I’m not receiving metrics for push notifications for iOS users.” The tool will return detailed steps to help you find and troubleshoot problems. ### Capture logs[](#capture-logs) Logs help us pinpoint the problem and find a solution. 1. **Enable debug logging in your app**.  You should not use debug mode in your production app. Remember to disable debug logging before you release your app to the App Store. ```javascript import { CustomerIO, CioConfig, CioLogLevel } from 'customerio-reactnative'; const config: CioConfig = { CioApiKey: 'Your CDP API Key', logLevel: CioLogLevel.Debug, } CustomerIO.initialize(config) ; ``` 2. Build and run your app on a physical device or emulator. 3. In the console, run: ```fallback react-native log-ios react-native log-android ``` 4. Export your log to a text file and send it to our Support team at [[email protected]](mailto:[email protected]). In your message, describe your problem and provide relevant information about: * The version of the SDK you’re using. * The type of problem you’ve encountered. * An existing GitHub issue URL or existing support email so we know what these log files are in reference to. ## NaN, infinite, or imaginary number values[](#nan-infinite-or-imaginary-number-values) Customer.io doesn’t handle invalid JSON values in your payloads, like `NaN`, infinite, or imaginary number values. If you send these values in `identify`, `track`, `screen`, or similar calls, we’ll drop them and record errors. While we drop invalid values, we don’t drop the entire payload. The operation itself will still succeed. For example, if you send an identify call with two attributes, one of which is a `NaN` value, we’ll drop the `NaN` value, but the identify call succeeds with the other attribute. ## Push notification issues[](#push-notification-issues) ### Problems with rich push notifications (images, delivered metrics, etc)[](#problems-with-rich-push-notifications-images-delivered-metrics-etc) If you have trouble with rich push features, like images not showing up in your push notifications, delivery metrics not being reported when a push notification is visible on the device, and so on, it’s possible that you either need to re-create your NSE target to support rich notifications your you may not have embeded the `NotificationServiceExtension` (NSE) at all. 1. Remove your current NSE extension. 1. In XCode, select your project. 2. Go to the *Signing & Capabilities* tab. 3. Click the NotificationServiceExtension target; it has a bell icon next to it. 4. Click the minus sign to remove the target 5. Confirm the **Delete** operation. [![an image illustrating the 5 clicks you'll perform to delete your NSE target.](https://customer.io/images/delete-nse-1.png)](#c66794617622894d2a61b5a026240a3b-lightbox) 2. Remove existing NSE files. 1. Right click the `NotificationServiceExtension` folder in your project and select **Delete**. 2. Confirm **Move to Trash**. [![an image illustrating the steps you'll take to delete NSE files.](https://customer.io/images/delete-nse-2.png)](#6a5a6ee211296a0c49beb046b941bfd8-lightbox) 3. Recreate the notification service extension, following instructions for your framework. When You create your target NSE file, make sure you select your app’s name from the *Embed in Application* dropdown. [![xcode-servicenotification3.png](https://customer.io/images/xcode-servicenotification3.png)](#97f7eea0f5f268a29a24b1bdea3c767c-lightbox) 4. Then add the required files: * [React Native](/integrations/sdk/react-native/push-notifications/push/#integrate-push-capabilities-in-your-app) * [Flutter](/integrations/sdk/flutter/push-notifications/push-setup/#swift-push) * Expo (does this automatically) * [iOS](/integrations/sdk/ios/push/#rich-push) 5. After all files are added, go to the NSE target and, under the **General** tab, check **Deployment Target** and set it to a value that is identical to your host app’s iOS version. [![troubleshoot-nse.png](https://customer.io/images/troubleshoot-nse.png)](#7e7c735e2a16c72a6db71ef6cf9fc50b-lightbox) When you create a new target, by default, XCode sets the highest version of deployment target version available. While testing if your device’s iOS version is lower than this deployment target, then the NSE won’t be connected to the main target and you won’t receive rich push notifications. Then you can build and run your app to test if you can receive a rich push notification. ### Why aren’t devices added to people in Production builds?[](#why-arent-devices-added-to-people-in-production-builds) If you see devices register successfully on your Staging builds, but not in Production or TestFlight builds, there might be an issue with your project setup. Check that the Push capability is enabled for both Release and Debug modes in your project. You might also need to enable the *Background Modes (Remote Notifications)* capability, depending on your project setup and messaging needs. ### Image display issues[](#image-display-issues) If you’re having trouble, try using our test image in a message! If it works, then there’s likely a problem with your original image. [![a test image of a bird that we know will work with all push notifications](https://customer.io/images/push-image-control.jpg)](#880a298d31bff7acc0ac56edbb6a6dc6-lightbox) Android and iOS devices support different image sizes and formats. In general, you should stick to the smallest size (under 1 MB—the limit for Android devices) and common formats (PNG, JPEG). iOS Android In-App (all platforms) **Format** JPEG, PNG, BMP, GIF JPEG, PNG, BMP JPEG, PNG, GIF **Maximum size** 10 MB\* 1 MB **Maximum resolution** 2048 x 1024 px 1038 x 1038 px \*For linked media only. If you host images in our Asset Library, you’re limited to 3MB per image. ### Try updating iOS package dependencies[](#try-updating-ios-package-dependencies) This SDK uses our iOS push package. In some cases, we may make fixes in our iOS packages that fix downstream issues in, or expose new features to this SDK. You can update the version in your podfile and then run the following command to get the latest iOS packages. Our instructions above list out the full version of the iOS push package. If you want to automatically increment packages, you can remove the patch and minor build numbers (the second and third parts of the version number), and `pod update` will automatically fetch the latest package versions. However, please understand that fetching the latest versions can cause build issues if the latest iOS package doesn’t agree with code in your app! ```shell pod update --repo-update --project-directory=ios ``` ### Why didn’t everybody in my segment get a push notification?[](#why-didnt-everybody-in-my-segment-get-a-push-notification) If your [segmentA group of people who match a series of conditions. People enter and exit the segment automatically when they match or stop matching conditions.](/journeys/data-driven-segments/) doesn’t specify people who have an existing device, it’s likely that people entered your segment without using your app. If you send a push notification to such a segment, the “Sent” count will probably show fewer sends than there were people in your segment. ### Why are messages sent but not delivered or opened?[](#why-are-messages-sent-but-not-delivered-or-opened) The *sent* status means that we sent a message to your delivery provider—APNS or FCM. It’ll be marked *delivered* or *opened* when the delivery provider forwards the message to the device and the SDK reports the metric back to Customer.io. If a person turned their device off or put it in airplane mode, they won’t receive your push notification until they’re back on a network.  Make sure you’ve configured your app to track metrics If your app isn’t set up to capture push metrics, your app will *never* report `delivered` or `opened` metrics! ### Why don’t my messages play sounds?[](#why-dont-my-messages-play-sounds) When you send a push notification to iOS devices that uses our SDK, you can opt to send the *Default* system sound or no sound at all. If your audience’s phone is set to vibrate, or they’ve disabled sound permissions for your app, the *Default* setting will cause the device to vibrate rather than playing a sound. In most cases, you should use the *Default* sound setting to make sure your audience hears (or feels) your message. But, before you send sound, you should understand: 1. Your app needs permission from your users to play sounds. This is done by your app, not our SDKs. [Here’s an example from our iOS sample app](https://github.com/customerio/customerio-ios/blob/main/Apps/APN-UIKit/APN%20UIKit/Util/NotificationUtil.swift#L12-L13) showing how to request sound permissions. 2. iOS users can go into *System Settings* and disable sound permissions for your app. Enabling the *Default* setting doesn’t guarantee that your audience hears a sound when your message is delivered!  We don’t support custom sounds yet If you want to send a custom sound, you’ll need to handle it on your own, outside the SDK and use a custom payload when you set up your push notifications. ### FCM `SENDER_ID_MISMATCH` error[](#fcm-sender_id_mismatch-error) This error occurs when the FCM Sender ID in your app does not match the Sender ID in your Firebase project. To resolve this issue, you’ll need to ensure that the Sender ID in your app matches the Sender ID in your Firebase project. 1. Check that you uploaded the correct JSON certificate to Customer.io. If your JSON certificate represents the wrong Firebase project, you may see this error. 2. Verify that the Sender ID in your app matches the Sender ID in your Firebase project. 3. If you imported devices (device tokens) from a previous project, make sure that you imported tokens from the correct Firebase project. If the tokens represent a different app than the one you send push notifications to, you’ll see this error. ### Error: Push notifications not working[](#error-push-notifications-not-working) If push notifications don’t work, make sure that you’ve initialized the Customer.io SDK in your `AppDelegate.swift` file. You must initialize the SDK in the `application(_:didFinishLaunchingWithOptions:)` method. ```swift @main class AppDelegateWithCioIntegration: CioAppDelegateWrapper {} class AppDelegate: NSObject, UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { ... // Initialize the Customer.io SDK for push notifications MessagingPushAPN.initialize(withConfig: MessagingPushConfigBuilder().build()) return true } } ``` ### Deep linking to iOS when your app is killed[](#troubleshoot-ios-deep-links) There’s a known issue preventing deep links from working when your app is closed on iOS devices. When the app is in a closed state, the native click event fires before the app’s lifecycle begins. We recommend a workaround: 1. Update `didFinishLaunchingWithOptions` in your `AppDelegate.swift` file with the code below. We extract the deep link from the push notification payload and add it to the launch options, ensuring that your React Native app receives the link when it starts. ```swift func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let delegate = ReactNativeDelegate() let factory = RCTReactNativeFactory(delegate: delegate) ... if var launchOptions = launchOptions, let remotePush = launchOptions[UIApplication.LaunchOptionsKey.remoteNotification] as? [String: [String: [String: String]]], let link = remotePush["CIO"]?["push"]?["link"], let url = URL(string:link), launchOptions[UIApplication.LaunchOptionsKey.url] == nil { launchOptions[UIApplication.LaunchOptionsKey.url] = url } let appName = Bundle.main.displayName factory.startReactNative( withModuleName: appName, in: window, initialProperties: ["appName": appName], launchOptions: launchOptions ) ... } ``` ### Compiler error: ‘X’ is unavailable in application extensions for iOS[](#compiler-error-x-is-unavailable-in-application-extensions-for-ios) This error occasionally occurs when users add a notification extension to handle rich push messages. If you see this error, try the following steps: 1. Add this code to the end of your `Podfile`: ```ruby post_install do |installer| installer.pods_project.targets.each do |target| if target.name.start_with?('CustomerIO') puts "Modifying target #{target.name} with workaround" target.build_configurations.each do |config| puts "Setting build config settings for #{target.name}" config.build_settings['APPLICATION_EXTENSION_API_ONLY'] ||= 'NO' end end end end ``` 2. In the root directory of your app, run `pod install --project-directory=ios`. This command will apply the above workaround to your project. 3. Try to compile your app again. *If you still see the error message*, it’s likely that the error you see is related to a different SDK that you use in your app and not the Customer.io SDK. We suggest that you contact the developers of the SDK that you see in the error message for help. *If you don’t see an error message*, [send our technical support team a message](mailto:[email protected]) with: * The error message that you see when compiling your app. * The contents of your `ios/Podfile` and `ios/Podfile.lock` files. * The version of the React Native SDK that you are using. ### Deep links on iOS only open in a browser[](#deep-links-on-ios-only-open-in-a-browser) It sounds like you want to use [universal links](/universal-links/#deep-links-vs-universal-links)—links that go to your app if a person has your app installed and to your website if they don’t. Universal links are a bit different than your average deep link and require a little bit of additional setup. ## In-App message issues[](#in-app-message-issues) ### My in-app messages are sent but not delivered[](#my-in-app-messages-are-sent-but-not-delivered) People won’t get your message until they open your app. If you use page rules, they won’t see your message until they visit the right screen(s), so delivery times for in-app messages can vary significantly from other types of messages. --- ## In app messages > In app actions **Source:** /integrations/sdk/react-native/in-app-messages/in-app-actions # In-app event listeners ## How it works[](#how-it-works) In-app messages often have a call to action. Most basic actions are handled automatically by the SDK. For example, if you set a call-to-action button to open a web page, the SDK will open the web page when the user taps the button. But you can also set up custom actions that require your app to handle the response. If you set up custom actions, you’ll need to handle the action yourself and dismiss the resulting message when you’re done with it. ### Handle responses to messages (event listeners)[](#event-listeners) You can set up event listeners to handle your audience’s response to your messages. For example, you might run different code in your app when your audience taps a button in your message or when they dismiss the message without tapping a button. You can listen for four different events: * `messageShown`: a message is “sent” and appears to a user * `messageDismissed`: the user closes the message (by tapping an element that uses the `close` action) * `errorWithMessage`: the message itself produces an error—this probably prevents the message from appearing to the user * `messageActionTaken`: the user performs an action in the message. After you initialize the SDK, you can register an event listener to subscribe to in-app events. In the code below, `event` is an instance of `InAppMessageEvent` containing details about the in-app message, e.g. `messageId`, `deliveryId`. ```javascript import { CustomerIO, InAppMessageEventType } from "customerio-reactnative"; CustomerIO.inAppMessaging.registerEventsListener((event) => { switch (event.eventType) { case InAppMessageEventType.messageShown: // handle message shown break; case InAppMessageEventType.messageDismissed: // handle message dismissed break; case InAppMessageEventType.errorWithMessage: // handle message error break; case InAppMessageEventType.messageActionTaken: // event.actionValue => The type of action that triggered the event. // event.actionName => The name of the action specified when building the in-app message. // handle message action break; } }); ``` ## Handling custom actions[](#handling-custom-actions) When you set up an in-app message, you can determine the “action” to take when someone taps a button, taps your message, etc. In most cases, you’ll want to deep link to a screen, etc. But, in some cases, you might want to execute some custom action or code—like requesting that a user opts into push notifications or enables a particular setting. In these cases, you’ll want to use the `messageActionTaken` event listener and listen for custom action names or values to execute code. While you’ll have to write custom code to handle custom actions, the SDK helps you [listen for in-app message events](#event-listeners) including your custom action, so you know when to execute your custom code. 1. When you [add an action to an in-app message](/journeys/in-app-messages/) in Customer.io, select *Custom Action* and set your Action’s *Name* and value. The *Name* corresponds to the `actionName`, and the value represents the `actionValue` in your event listener. [![Set up a custom in-app action](https://customer.io/images/in-app-custom-action.png)](#1005ee0b179f3999e81398a57e369557-lightbox) 2. [Register an event listener](#event-listener) for `MessageActionTaken`, and listen for the `actionName` or `actionValue` you set up in the previous step.  Use names and values exactly as entered We don’t modify your action’s name or value, so you’ll need to match the case of names or values exactly as entered in your *Custom Action*. 3. When someone receives a message and invokes the action (tapping a button, tapping a message, etc), your app will perform the custom action. ## Dismiss in-app message[](#dismiss-in-app-message) You can dismiss the currently display in-app message with the following method. This can be particularly useful to dismiss in-app messages when your audience clicks or taps custom actions. ```javascript CustomerIO.inAppMessaging.dismissMessage(); ``` ## Deep links[](#deep-links) You can open deep links when a user clicks actions inside in-app messages. Setting up deep links for in-app messages is the same as [setting up deep links for push notifications](/integrations/sdk/react-native/push-notifications/deep-links/). --- ## In app messages > Inline in app **Source:** /integrations/sdk/react-native/in-app-messages/inline-in-app # Inline in-app messages Inline in-app messages help you send dynamic content into your app. The messages can look and feel like a part of your app, but provide fresh and timely content without requiring app updates. ## How it works[](#how-it-works) An inline message targets a specific view in your app. Basically, you’ll create an empty placeholder view in your app’s UI, and we’ll fill it with the content of your message. This makes it easy to show dynamic content in your app without development effort. You don’t need to force an update every time you want to talk to your audience. *And*, unlike push notifications, banners, toasts, and so on, in-line messages can look like natural parts of your app. ## 1\. Add View to your app UI to support inline messages[](#1-add-view-to-your-app-ui-to-support-inline-messages) You’ll need to include a UI element in your app UI to render inline messages. The view will automatically adjust its height when messages are loaded or interacted with.  We’ve set up examples in [our sample apps](https://github.com/customerio/customerio-reactnative/tree/main/example) that might help if you want to see a real-world implementation of this feature. Add the `InlineInAppMessageView` component to your React Native app: ```jsx import { InlineInAppMessageView } from 'customerio-reactnative'; function MyComponent() { return ( { console.log('Action clicked:', { message, actionValue, actionName }); }} /> ); } ``` ### View layout[](#view-layout) The `InlineInAppMessageView` automatically adjusts its height at runtime when messages load or users interact with them. You should avoid setting a fixed height on this component as it might interfere with message rendering. You’re responsible for setting layout styles to position your view correctly (width, margins, padding, and so on). The component will handle its own height dynamically. ## 2\. Build and send your message[](#2-build-and-send-your-message) When you add an in-app message to a broadcast or campaign in Customer.io: 1. Set the **Display** to **Inline** and set the **Element ID** to the ID you set in your app. If the editor says that the inline display feature is *Web/iOS only*, don’t worry about that. We’re working on updating this UI. 2. (Optional) If you send multiple messages to the same Element ID, you’ll also want to set the **Priority**. This determines which message we’ll show to your audience first, if there are multiple messages in the queue. Then craft and send your message! [![in-app message settings with the To field set to ID and the display setting set to inline](https://customer.io/images/in-app-inline.png)](#74810b23329cd479a11edfdda9f818e0-lightbox) ## Handling custom actions[](#handling-custom-actions) When you set up an in-app message, you can determine the “action” to take when someone taps a button, taps your message, etc. In most cases, you’ll want to deep link to a screen, etc. But, in some cases, you might want to execute some custom action or code—like requesting that a user opts into push notifications or enables a particular setting. While you’ll have to write custom code to handle custom actions, the SDK helps you listen for in-app message events including your custom action, so you know when to execute your custom code. Follow the steps below to implement custom actions for inline messages: ### 1\. Compose an in-app message with a custom action[](#1-compose-an-in-app-message-with-a-custom-action) When you [add an action to an in-app message](/journeys/in-app-messages/) in Customer.io, select *Custom Action* and set your Action’s *Name* and value. The *Name* corresponds to the `actionName`, and the value represents the `actionValue` in your event listener. [![Set up a custom in-app action](https://customer.io/images/in-app-custom-action.png)](#1005ee0b179f3999e81398a57e369557-lightbox) ### 2\. Listen for events[](#2-listen-for-events) There are two ways to listen for these click events in inline in-app messages. 1. Register a callback with your inline view: ```jsx import { InlineInAppMessageView } from 'customerio-reactnative'; function MyComponent() { const handleActionClick = (message, actionValue, actionName) => { // Perform some logic when people tap an action button. // Example code handling button tap: switch (actionValue) { // use actionValue or actionName, depending on how you composed the in-app message. case "enable-auto-renew": // Perform the action to enable auto-renew enableAutoRenew(actionName); break; // You can add more cases here for other actions default: // Handle unknown actions or do nothing console.log("Unknown action:", actionValue); } }; return ( ); } ``` 2. Register a global SDK event listener. When you register an [event listener](../in-app-event-listeners/#event-listeners) with the SDK, we’ll call the `messageActionTaken` event listener. We call this event listener for both modal and inline in-app message types, so you can reuse logic for inline and non-inline messages if you want. ## Handle responses to messages (event listeners)[](#event-listeners) Like [modal in-app messages](../in-app-event-listeners/#event-listeners), you can set up event listeners to handle your audience’s response to your messages. For inline messages, you can listen for three different events: * `messageShown`: a message is “sent” and appears to a user. * `errorWithMessage`: the message itself produces an error—this probably prevents the message from appearing to the user. * `messageActionTaken`: the user performs an action in the message. As [shown above](#2-listen-for-events), this is only called if the View instance doesn’t have an `onActionClick` callback set. Unlike modal in-app messages, you’ll notice that there’s no `messageDismissed` event. This is because inline messages don’t really have a concept of dismissal like modal messages do. They’re meant to be a part of your app! --- ## In app messages > Set up in app **Source:** /integrations/sdk/react-native/in-app-messages/set-up-in-app # Set up in-app messages ## How it works[](#how-it-works) An in-app message is a message that people see within the app. People won’t see your in-app messages until they open your app. If you set an *expiry* period for your message, and that time elapses before someone opens your app, they won’t see your message. You can also set *page rules* to display your in-app messages when people visit specific pages in your app. However, to take advantage of page rules, you need to use [screen tracking](/integrations/sdk/react-native/tracking/screen-events) features. Screen tracking tells us the names of your pages and which page a person is on, so we can display in-app messages on the correct pages in your app. ## Set up in-app messaging[](#set-up-in-app-messaging) In-app messages are disabled by default. Just set the `inApp.siteId` option in your `CioConfig`, and your app will be able to receive in-app messages. 1. Go to and select **Workspace Settings** in the upper-right corner of the Customer.io app and go to [**API and Webhook Credentials**](https://fly.customer.io/workspaces/last/settings/api_credentials). 2. Copy the **Site ID** for the set of credentials that you want to send your in-app messages from. If you don’t have a set of credentials, click **Create Tracking API Key** to generate them. [![find your site ID](https://customer.io/images/cdp-js-site-id.png)](#64d2b27827ffddb00dc77b851a7a6854-lightbox) ```javascript const config: CioConfig = { cdpApiKey: 'cdp_api_key', region: CioRegion.US, inApp: { siteId: 'site_id', } }; ``` ## Page rules[](#page-rules) You can set page rules when you create an in-app message. A page rule determines the page that your audience must visit in your app to see your message. However, before you can take advantage of page rules, you need to: 1. Track screens in your app. See the [Track Events](/integrations/sdk/react-native/tracking/track-events/#manual-screenview) page for help sending `screen` events. 2. Provide page names to whomever sets up in-app messages in fly.customer.io. If we don’t recognize the page that you set for a page rule, your audience will never see your message. [![Set up page rules to limit in app messages by page](https://customer.io/images/in-app-page-rule.png)](#a55af0f9917c15a7b484c9df200f448d-lightbox) Keep in mind: page rules are case sensitive. Make sure your page rules match the casing of the `title` in your `screen` events. [![The first page rule is Web contains /dashboard. The second page rule is iOS contains Dashboard.](https://customer.io/images/page-rule-case-sensitive.png)](#ba51bbdc9b4c25b5402f99a8a9d30245-lightbox) ## Anonymous messages[](#anonymous-messages) As of version 4.11, you can send [anonymous in-app messages](/journeys/anonymous-in-app/). These are messages that are sent *only* to people you haven’t identified yet. You *can* use lead forms in anonymous messages to capture leads and potentially identify people when they submit your form. For example, you could use a lead form and offer a coupon or newsletter to people who provide their email addresses. See [Lead forms](/journeys/messages-and-webhooks/in-app/lead-form/) for more information. --- ## Push notifications > Deep links **Source:** /integrations/sdk/react-native/push-notifications/deep-links # Deep Links ## How it works[](#how-it-works) Deep links are the links that directs users to a specific location within a mobile app. When you set up your notification, you can set a “deep link.” When your audience taps the notification, the SDK will route users to the right place. Deep links help make your message meaningful, with a call to action that makes it easier, and more likely, for your audience to follow. For example, if you send a push notification about a sale, you can send a deep link that takes your audience directly to the sale page in your app. However, to make deep links work, you’ll have to handle them in your app. We’ve provided instructions below to handle deep links in both Android and iOS versions of your app. ## Android: set up deep links[](#deep-links-android) 1. Deep links provide a way to link to a screen in your app. You’ll set up deep links by adding [intent filters](https://developer.android.com/training/app-links/deep-linking) to the `AndroidManifest.xml` file. ```xml ``` 2. Now you’re ready to handle deep links. In your `App.js` file or anywhere you handle navigation, you’ll add code that looks like this. ```javascript import { NavigationContainer } from '@react-navigation/native'; const config = { screens: { Home: { path: 'home/:id?', parse: { id: (id: String) => `${id}`, }, }, } }; const linking = { prefixes: ['amiapp://'], config }; return ( ... ) ``` After you set up intent filters, you can test your implementation with the Rich Push editor or the payloads included for [Testing push notifications](https://customer.io/sdk/react-native/push/#rich-push-payloads). ### Push Click Behavior[](#push-click-behavior) The `push.android.pushClickBehavior` config option controls how your app behaves when your audience taps push notifications on Android devices. The SDK automatically tracks `Opened` metrics for all options. ```javascript const config: CioConfig = { cdpApiKey: 'cdp_api_key', region: CioRegion.US, inApp: { siteId: 'site_id', }, push: { android: { pushClickBehavior: PushClickBehaviorAndroid.ActivityPreventRestart } } }; CustomerIO.initialize(config) ``` The available options are: * `ActivityPreventRestart` **(Default)**: If your app is already in the foreground, the SDK will not re-create your app when your audience clicks a push notification. Instead, the SDK will reuse the existing activity. If your app *is not* in the foreground, we’ll launch a new instance of your deep-linked activity. We recommend that you use this setting if your app has screens that your audience shouldn’t navigate away from—like a shopping cart screen. * `ActivityNoFlags`: If your app is in the foreground, the SDK will re-create your app when your audience clicks a notification. The activity is added on top of the app’s existing navigation stack, so if your audience tries to go back, they will go back to where they previously were. * `ResetTaskStack`: No matter what state your app is in (foreground, background, killed), the SDK will re-create your app when your audience clicks a push notification. Whether your app is in the foreground or background, the state of your app will be killed so your audience cannot go back to the previous screen if they press the back button. ## iOS: Set up deep links[](#deep-links-ios) Deep links let you open a specific page in your app instead of opening the device’s web browser. Want to open a screen in your app or perform an action when a push notification or in-app button is clicked? Deep links work great for this! Setup deep linking in your app. There are two ways to do this; you can do both if you want. * **Universal Links**: universal links let you open your mobile app instead of a web browser when someone interacts with a *URL on your website*. For example: `https://your-social-media-app.com/profile?username=dana`—notice how this URL is the same format as a webpage. * **App scheme**: app scheme deep links are quick and easy to setup. Example of an app scheme deep link: `your-social-media-app://profile?username=dana`. Notice how this URL is *not* a URL that could show a webpage if your mobile app is not installed. Universal Links provide a fallback for links if your audience doesn’t have your app installed, but they take longer to set up than *App Scheme* deep links. App Scheme links are easier to set up but won’t work if your audience doesn’t have your app installed. ### Setup App Scheme deep links[](#app-scheme-deep-links) After you [set up push notifications](/integrations/sdk/react-native/push/#register-push) you can enable deep links in rich push notifications. There are a number of ways to enable deep links. Our example below uses `@react-navigation` with a `config` and `prefix` to automatically set paths. The paths are the values you’d use in your push payload to send a link. However, before you can do this, you need to set up your app link scheme for iOS. [Learn more about URL schemes for iOS apps](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app).  There’s an issue deep linking into iOS when the app is closed In iOS, deep link click events won’t fire when your app is closed. See our [troubleshooting section](/integrations/sdk/react-native/troubleshooting/#troubleshoot-ios-deep-links) for a workaround to this issue. 1. Open your project in Xcode and select your root project in the Project Navigator. 2. Go to the **Info** tab. 3. Scroll down to the options in the *Info* tab and expand **URL Types**. 4. Click to add a new, untitled schema. 5. Under **Identifier** and **URL Schemes**, add the name of your schema. [![set up your deep link scheme in xcode](https://customer.io/images/xcode-deep-link-schema.png)](#c619327cb81e2efb9935b0551472c376-lightbox) 6. Open your `AppDelegate.swift` file and add the code below. Note that the Customer.io SDK automatically forwards deep links from Customer.io push notifications to the `application(:open:options:)` method. For push notifications from other providers, you still need to handle deep links manually in the `userNotificationCenter(didReceive:withCompletionHandler:)` method. ```swift import UIKit import CioMessagingPushAPN import React @main class AppDelegateWithCioIntegration: CioAppDelegateWrapper {} class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { // Handle deep links return RCTLinkingManager.application(app, open: url, options: options) } } ``` 7. Now you’re ready to handle deep links. In your `App.js` file or anywhere you handle navigation, you’ll add code that looks like this. ```javascript import { NavigationContainer } from '@react-navigation/native'; const config = { screens: { Home: { path: 'home/:id?', parse: { id: (id: String) => `${id}`, }, }, } }; const linking = { prefixes: ['amiapp://'], config }; return ( ... ) ``` ### Set up Universal Links[](#universal-links-deep-links) Follow [React Native’s documentation](https://reactnative.dev/docs/linking) to implement Universal Links in your app. --- ## Push notifications > Multiple push providers **Source:** /integrations/sdk/react-native/push-notifications/multiple-push-providers # Handling Multiple Push Providers ## How to handle multiple push providers[](#how-to-handle-multiple-push-providers) If Customer.io is the only SDK that you use in your app to display push notifications, then you don’t need to do anything special to display push notifications. But, if you use another module in your app that can display push notifications like `expo-notifications`, `react-native-push-notification`, or `rnfirebase`, these modules can take over push handling by default and prevent your app from receiving push notifications from Customer.io. You can solve this problem using one (and **only one**) of the methods below, but we typically recommend [the first option](#cio-process-notification-payloads), because it doesn’t require you to write native code! Please note that the following methods will always return `true` for iOS. ## Option 1 (Recommended): Set Customer.io SDK to handle push clicks[](#cio-process-notification-payloads) You can pass the payloads of other message services to Customer.io whenever a device receives a notification, and our SDK can process it for you. The SDK exposes the `onMessageReceived` method for this that takes two arguments: * a `message.data` object containing the incoming notification payload * a `handleNotificationTrigger` boolean indicating whether or not to trigger a notification. * `true` (default) means that the Customer.io SDK will generate the notification and track associated metrics. * `false` means that the SDK will process the notification to track metrics but *will not* generate a notification on the device. You’ll use the `onMessageReceived` like this: ```javascript CustomerIO.pushMessaging.onMessageReceived(message).then(handled => { // If true, the push was a Customer.io notification and handled by our SDK // Otherwise, `handled` is false }); ``` You can pass values in `onMessageReceived` by listening to notification events exposed by other SDKs. Make sure that you add listeners in the right places to process notifications that your app receives when it’s in the foreground and add background listeners that might be required by other SDK to process notifications that your app receives when it’s in background/killed state. If you *always* send rich push messages (with `image` and/or `link`), adding event listeners is enough. But if you send custom push payloads using the `notification` object or send simple push messages (with just a `body` and `title`), you may get duplicate notifications when your app is backgrounded because Firebase itself displays notifications sent using the `notification` object. To avoid this, You can pass `false` in `handleNotificationTrigger` to track metrics for simple and custom payload push notifications. To simplify this behavior, the SDK also exposes an `onBackgroundMessageReceived` method that automatically suppresses pushes with the notification object when your app is in background. If you use `rnfirebase`, you can setup listeners like this: Foreground Listener #### Foreground Listener[](#Foreground Listener) To listen to messages in the foreground, set `onMessage` listener where appropriate: ```javascript useEffect(() => { const unsubscribe = messaging().onMessage(async remoteMessage => { CustomerIO.pushMessaging.onMessageReceived(remoteMessage).then(handled => { // If true, the push was a Customer.io notification and handled by our SDK // Otherwise, `handled` is false }); }); return unsubscribe; }, []); ``` Background Listener #### Background Listener[](#Background Listener) To listen to messages when app is in background/killed state, set `setBackgroundMessageHandler` in your `index.js` file ```javascript messaging().setBackgroundMessageHandler(async remoteMessage => { CustomerIO.pushMessaging.onBackgroundMessageReceived(remoteMessage).then(handled => { // If true, the push was a Customer.io notification and handled by our SDK // Otherwise, `handled` is false }); }); ``` ## Option 2: Register Customer.io Messaging Service[](#register-messaging-service) You can register Customer.io’s messaging service in your `Manifest` file so that we handle all notifications for your app. You can do this by adding the following code under the `` tag in the `AndroidManifest.xml` file in your app’s `android` folder. ```xml ```  The Customer.io SDK will handle all your push notifications The code above hands all push notifications responsibility to our SDK, meaning: * Your app will receive all simple and rich push notifications from Customer.io. * When your app is in the background, it can receive push notifications with a `notification` payload from other services. * Your app **cannot receive** `data`\-only push notifications from another service. ## Manually track push metrics[](#manual-push-metrics) If you need to manually track push metrics when you use multiple push providers (like when you display notifications yourself or use another library), you can parse a push notification payload and send `opened` or `delivered` events to the SDK in relevant callbacks: ```javascript CustomerIO.trackMetric({ deliveryID: deliveryID, deviceToken: deviceToken, event: MetricEvent.Opened, }); ``` The `trackMetric` method requires the following parameters: * `deliveryID`: The delivery ID extracted from the push notification payload * `deviceToken`: The device token extracted from the push notification payload * `event`: The metric event type, either `MetricEvent.Opened` or `MetricEvent.Delivered` --- ## Push notifications > Push **Source:** /integrations/sdk/react-native/push-notifications/push # Set up push notifications Our React Native SDK supports push notifications over APN or FCM—including rich push messages with links and images. Use this page to add support for your push provider and set your app up to receive push notifications. ## How it works[](#how-it-works) Under the hood, our React Native SDK takes advantage of our native Android and iOS SDKs. This helps us keep the React Native SDK up to date. But, for now, it also means you’ll need to add a *bit* of code to support your iOS users. For Android, you’re ready to go if you followed our [getting started instructions](/integrations/sdk/react-native/getting-started/#install). Before a device can receive a push notification, you must: 1. (iOS) [Add push notification capabilities in XCode](#add-push-capabilities-in-xcode). 2. (iOS) [Integrate push notifications](#register-push): code samples on this page help you do that. 3. [Identify a person](/integrations/sdk/react-native/identify/). This associates a token with the person; you can’t send push notifications to a device until you identify the recipient. 4. [Request, or check for, push notification permissions](#prompt-users-for-to-opt-into-push-notifications). If your app’s user doesn’t grant permission, notifications will not appear in the system tray. While push providers support a number of features in their payloads, **our React Native package only supports deep links and images right now**. If you want to include action buttons or other rich push features, you need to add your own custom code. When writing your own custom code, we recommend that you use our SDK as it is much easier to extend than writing your own code from scratch.  Did you already set up your push providers? To send, test, and receive push notifications, you’ll need to set up your push notification service(s) in Customer.io. If you haven’t already, set up [Apple Push Notification Service (APNs)](/journeys/push-getting-started/#for-ios) and/or [Firebase Cloud Messaging (FCM)](/push-getting-started/#for-android). ## Set up push on Android[](#register-push-android) If you followed our [Getting Started instructions](/integrations/sdk/react-native/quick-start-guide/#install), you’re already set up to send standard push notifications to Android devices. ## Set up push on iOS[](#register-push) You’ll need to add some additional code to support push notifications for iOS. You’ll need to add push capabilities in XCode and integrate push capabilities in your app. ### Add push capabilities in Xcode[](#add-push-capabilities-in-xcode) Before you can work with push notifications, you need to add Push Notification capabilities to your project in XCode. 1. In your React Native project, go to the `ios` subfolder and open `.xcworkspace`. 2. Select your project, and then under *Targets*, select your main app. 3. Click the **Signing & Capabilities** tab 4. Click **Capability**. 5. Add **Push Notifications** to your app. When you’re done, you’ll see **Push Notifications** added to your app’s capabilities, but there are still a few more steps to finish setting things up. [![add push notification capabilities to your app](https://customer.io/images/react-native-xcode-push.png)](#b837646bba75943a4f08d0fee059210c-lightbox) 6. Go to **File** > **New** > **Target**. [![xcode-servicenotification1.png](https://customer.io/images/xcode-servicenotification1.png)](#64d64173bde7b46bad5fc1f14cc8f36a-lightbox) 7. Select **Notification Service Extension** and click **Next**. [![xcode-servicenotification2.png](https://customer.io/images/xcode-servicenotification2.png)](#6413f7694da0358105aca5a02cf835dc-lightbox) 8. Enter a product name, like *NotificationServiceExtension* (which we use in our examples on this page) and **set the *Language* to *Swift* or *Objective C* based on the language you use for native iOS files.** Click **Finish**. [![xcode-servicenotification3.png](https://customer.io/images/xcode-servicenotification3.png)](#97f7eea0f5f268a29a24b1bdea3c767c-lightbox) 9. When presented with the dialog below, click **Cancel**. This helps Xcode continue debugging your app and not just the extension you just added. [![xcode-servicenotification4.png](https://customer.io/images/xcode-servicenotification4.png)](#7a87192ad7f0dc9047625d6dfc407e77-lightbox) Now you have another target in your project navigator named `NotificationServiceExtension`. We’ll configure this extension when we [Integrate Push Notifications](#integrate-push-capabilities-in-your-app) in the following section. ### Integrate push capabilities in your app[](#integrate-push-capabilities-in-your-app) Pick your push provider (*APN* or *FCM*) and the language your native files are written in to get started (*Objective C* or *Swift*). #### APN/Objective-C[](#APN/Objective-C) 1. Open your `ios/Podfile` and add the Customer.io push dependency, highlighted here, to both your main target and `NotificationServiceExtension` target. ```ruby target 'SampleApp' do # Look for the main app target. # Make all file modifications after this line: config = use_native_modules! # Add the following line to add the Customer.io native dependency: pod 'customerio-reactnative/apn', :path => '../node_modules/customerio-reactnative' end # Next, copy and paste the code below to the bottom of your Podfile: target 'NotificationServiceExtension' do # Notice the '-richpush' in the line below. This line of code is different from what you added for your main target. pod 'customerio-reactnative-richpush/apn', :path => '../node_modules/customerio-reactnative' end ``` 2. Open your terminal, go to your project path and install the pods. ```bash pod install --project-directory=ios ``` 3. Open `ios/.xcworkspace` in Xcode, and add a new Swift file to your project. Copy the code here into your file. We’re calling our file `MyAppPushNotificationsHandler.swift` and the associated class `MyAppPushNotificationsHandler`, but you might want to rename things to fit your app. ```swift import Foundation import CioMessagingPushAPN @objc public class MyAppPushNotificationsHandler : NSObject { public override init() {} @objc(setupCustomerIOClickHandling) public func setupCustomerIOClickHandling() { // This line of code is required in order for the Customer.io SDK to handle push notification click events. // Initialize MessagingPushAPN module to automatically handle push notifications that originate from Customer.io MessagingPushAPN.initialize(withConfig: MessagingPushConfigBuilder().build()) } @objc(application:deviceToken:) public func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { // Register device to receive push notifications with device token MessagingPush.shared.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) } @objc(application:error:) public func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { MessagingPush.shared.application(application, didFailToRegisterForRemoteNotificationsWithError: error) } } ``` 4. Open your `ios/AppDelegate.mm` file and import your header file. The name of the header file will depend on your app’s main target name i.e. `YourMainTargetName-Swift.h` and is auto-created by Xcode. If you’re not a native iOS developer, the `.h` and `.mm` files represent interface and implementation respectively. It’s a convention of XCode to keep these files separate. ```obj-c #import "SampleApp-Swift.h" ``` 5. Inside AppDelegate’s `@implementation`, create an object of `MyAppPushNotificationsHandler` (remember to substitute the name of your handler). ```obj-c @implementation AppDelegate MyAppPushNotificationsHandler* pnHandlerObj = [[MyAppPushNotificationsHandler alloc] init]; ``` 6. Update `AppDelegate.mm` to register a device to the current app user and handle push notifications. ```obj-c - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { RCTAppSetupPrepareApp(application, true); NSMutableDictionary *modifiedLaunchOptions = [NSMutableDictionary dictionaryWithDictionary:launchOptions]; if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) { NSDictionary *pushContent = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; if (pushContent[@"CIO"] && pushContent[@"CIO"][@"push"] && pushContent[@"CIO"][@"push"][@"link"]) { NSString *initialURL = pushContent[@"CIO"][@"push"][@"link"]; if (!launchOptions[UIApplicationLaunchOptionsURLKey]) { modifiedLaunchOptions[UIApplicationLaunchOptionsURLKey] = [NSURL URLWithString:initialURL]; } } } RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:modifiedLaunchOptions]; #if RCT_NEW_ARCH_ENABLED _contextContainer = std::make_shared<:react::contextcontainer const>(); _reactNativeConfig = std::make_shared<:react::emptyreactnativeconfig const>(); _contextContainer->insert("ReactNativeConfig", _reactNativeConfig); _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer]; bridge.surfacePresenter = _bridgeAdapter.surfacePresenter; #endif NSDictionary *initProps = [self prepareInitialProps]; UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"SampleApp", initProps, true); [application registerForRemoteNotifications]; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; UIViewController *rootViewController = [UIViewController new]; rootViewController.view = rootView; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; [pnHandlerObj setupCustomerIOClickHandling]; [RNNotifications startMonitorNotifications]; return YES; } ... // Required to register device token. - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { // Register device to receive push notifications with device token [pnHandlerObj application:application deviceToken:deviceToken]; } // Required for the registration error event. - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [pnHandlerObj application:application error:error]; } ``` 7. In XCode, select your `NotificationServiceExtension`. Go to **File** > **New** > **File** > **Swift File** and click **Next**. Enter a file name, like `NotificationServicePushHandler`, and click **Create**. This adds a new swift file in your extension target. Copy the code on the right and paste it into this new file (which we’ve called `NotificationServicePushHandler.swift`) file—replacing everything in the file and update `Env.cdpApiKey` with your CDP API key. ```swift import CioMessagingPushAPN import Foundation import UserNotifications @objc public class NotificationServicePushHandler: NSObject { public override init() {} @objc(didReceive:withContentHandler:) public func didReceive( _ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void ) { MessagingPushAPN.initializeForExtension( withConfig: MessagingPushConfigBuilder(cdpApiKey: Env.cdpApiKey) // Optional: specify region where your Customer.io account is located (.US or .EU). Default: US //.region(.US) .build() ) MessagingPush.shared.didReceive(request, withContentHandler: contentHandler) } @objc(serviceExtensionTimeWillExpire) public func serviceExtensionTimeWillExpire() { MessagingPush.shared.serviceExtensionTimeWillExpire() } } ``` 8. Open your `NotificationService.m` file and copy the highlighted lines (beginning on line 2) into your file. The name of the header file on line 2 will depend on your extension’s name i.e. `YourNotificationServiceExtensionName-Swift.h` and is automatically created by Xcode. After this, you can run your app on a physical device and send yourself a push notification with images and deep links to test your implementation. You’ll have to use a physical device because simulators can’t receive push notifications. ```swift #import "NotificationService.h" #import "NotificationServiceExtension-Swift.h" @interface NotificationService () @end @implementation NotificationService // Create object of class NotificationServicePushHandler NotificationServicePushHandler* nsHandlerObj = nil; // Initialize the object + (void)initialize{ nsHandlerObj = [[NotificationServicePushHandler alloc] init]; } - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { [nsHandlerObj didReceive:request withContentHandler:contentHandler]; } - (void)serviceExtensionTimeWillExpire { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. [nsHandlerObj serviceExtensionTimeWillExpire]; } @end ``` #### APN/Swift[](#APN/Swift) 1. Open your `ios/Podfile` and add the Customer.io push dependency, highlighted here, to both your main target and `NotificationServiceExtension` target. ```ruby target 'SampleApp' do # Look for the main app target. # Make all file modifications after this line: config = use_native_modules! # Add the following line to add the Customer.io native dependency: pod 'customerio-reactnative/apn', :path => '../node_modules/customerio-reactnative' end # Next, copy and paste the code below to the bottom of your Podfile: target 'NotificationServiceExtension' do # Notice the '-richpush' in the line below. This line of code is different from what you added for your main target. pod 'customerio-reactnative-richpush/apn', :path => '../node_modules/customerio-reactnative' end ``` 2. Open your terminal, go to your project path and install the pods. ```bash pod install --project-directory=ios ``` 3. In your iOS subfolder, update your `AppDelegate.swift` file to use the Customer.io wrapper class that handles push notifications automatically. This approach replaces the need to manually implement push notification delegate methods. ```swift import UIKit import CioMessagingPushAPN import UserNotifications @main class AppDelegateWithCioIntegration: CioAppDelegateWrapper {} class AppDelegate: NSObject, UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { ... // Optional: add if you need UNUserNotificationCenterDelegate methods UNUserNotificationCenter.current().delegate = self // Initialize the Customer.io SDK for push notifications MessagingPushAPN.initialize(withConfig: MessagingPushConfigBuilder().build()) return true } } extension AppDelegate: UNUserNotificationCenterDelegate { // Optional: add this method if you want more control over notifications when your app is in the foreground func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.banner, .list, .badge, .sound]) } } ``` 4. Add a notification service extension to call the appropriate Customer.io functions. This lets your app display rich push notifications, including images, etc. See [Deep Links](/integrations/sdk/react-native/push-notifications/deep-links/) if you want to support deep links from push notifications. ```swift import CioMessagingPushAPN import Foundation import UserNotifications class NotificationService: UNNotificationServiceExtension { override func didReceive( _ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void ) { MessagingPushAPN.initializeForExtension( withConfig: MessagingPushConfigBuilder(cdpApiKey: Env.cdpApiKey) // Optional: specify region where your Customer.io account is located (.US or .EU). Default: US //.region(.US) .build() ) MessagingPush.shared.didReceive(request, withContentHandler: contentHandler) } override func serviceExtensionTimeWillExpire() { // Called just before the extension is terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. MessagingPush.shared.serviceExtensionTimeWillExpire() } } ``` #### FCM/Objective-C[](#FCM/Objective-C) 1. Open your `ios/Podfile` and add the Customer.io push dependency, highlighted here, to both your main target and `NotificationServiceExtension` target. ```ruby # Note: You may need to add this line, as required by FCM, to the top of your Podfile if you encounter errors during 'pod install' use_frameworks! :linkage => :static target 'YourApp' do # Look for the main app target. # Make all file modifications after this line: config = use_native_modules! # Add the following line to add the Customer.io native dependency: pod 'customerio-reactnative/fcm', :path => '../node_modules/customerio-reactnative' end # Next, copy and paste the code below to the bottom of your Podfile: target 'NotificationServiceExtension' do # Notice the '-richpush' in the line below. This line of code is different from what you added for your main target. pod 'customerio-reactnative-richpush/fcm', :path => '../node_modules/customerio-reactnative' end ``` 2. Open your terminal, go to your project path and install the pods. ```bash pod install --project-directory=ios ``` 3. Open `ios/.xcworkspace` in Xcode, and add a new Swift file to your project. Copy the code here into your file. We’re calling our file `MyAppPushNotificationsHandler.swift` and the associated class `MyAppPushNotificationsHandler`, but you might want to rename things to fit your app. ```swift import CioMessagingPushFCM import CioFirebaseWrapper import FirebaseMessaging import Foundation @objc public class MyAppPushNotificationsHandler: NSObject { public override init() {} @objc(setupCustomerIOClickHandling) public func setupCustomerIOClickHandling() { // Initialize MessagingPushFCM module to automatically handle your app’s push notifications that originate from Customer.io MessagingPushFCM.initialize(withConfig: MessagingPushConfigBuilder().build()) } @objc(didReceiveRegistrationToken:fcmToken:) public func didReceiveRegistrationToken(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { // Register device on receiving a device token (FCM) MessagingPush.shared.messaging(messaging, didReceiveRegistrationToken: fcmToken) } } ``` 4. Open `AppDelegate.h` and add the `FIRMessagingDelegate` import statement. If you’re not a native Objective-C developer, the `.h` and `.mm` files represent interface and implementation respectively. It’s a convention of XCode to keep these files separate. ```obj-c #import #import #import @interface AppDelegate : RCTAppDelegate @end ``` 5. Open your `ios/AppDelegate.mm` file and import your header file, as we’ve shown on line 2 and also import `FirebaseCore` as we’ve shown on line 5. The name of the header file will depend on your app’s main target name i.e. `YourMainTargetName-Swift.h` and is auto-created by Xcode. ```obj-c #import "AppDelegate.h" #import #import #import #import ``` 6. In your `AppDelegate.mm` file, create an object of your push notification handler. We called ours `MyAppPushNotificationsHandler`. ```obj-c @implementation AppDelegate // Create Object of class MyAppPushNotificationsHandler MyAppPushNotificationsHandler *pnHandlerObj = [[MyAppPushNotificationsHandler alloc] init]; ``` 7. Update `AppDelegate.mm` to configure Firebase and handle tokens. We’ve highlighted the code here to show what you need to add. ```obj-c - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.moduleName = @"FCMSampleApp"; // You can add your custom initial props in the dictionary below. // They will be passed down to the ViewController used by React Native. self.initialProps = @{}; // Configure Firebase [FIRApp configure]; // Set FCM messaging delegate [FIRMessaging messaging].delegate = self; // Use modifiedLaunchOptions for passing link to React Native bridge to sends users to the specified screen NSMutableDictionary *modifiedLaunchOptions = [NSMutableDictionary dictionaryWithDictionary:launchOptions]; if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) { NSDictionary *pushContent = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; if (pushContent[@"CIO"] && pushContent[@"CIO"][@"push"] && pushContent[@"CIO"][@"push"][@"link"]) { NSString *initialURL = pushContent[@"CIO"][@"push"][@"link"]; if (!launchOptions[UIApplicationLaunchOptionsURLKey]) { modifiedLaunchOptions[UIApplicationLaunchOptionsURLKey] = [NSURL URLWithString:initialURL]; } } } [pnHandlerObj setupCustomerIOClickHandling]; return [super application:application didFinishLaunchingWithOptions:modifiedLaunchOptions]; } ... @end ``` 8. In XCode, select your `NotificationServiceExtension`. Go to **File** > **New** > **File** > **Swift File** and click **Next**. Enter a file name, like `NotificationServicePushHandler`, and click **Create**. This adds a new swift file in your extension target. Copy this code into the new file and replace `Env.cdpApiKey` with your CDP API key. ```swift import CioMessagingPushFCM import CioFirebaseWrapper import Foundation import UserNotifications @objc public class MyAppNotificationServicePushHandler: NSObject { public override init() {} @objc(didReceive:withContentHandler:) public func didReceive( _ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void ) { MessagingPushFCM.initializeForExtension( withConfig: MessagingPushConfigBuilder(cdpApiKey: Env.cdpApiKey) // Optional: specify region where your Customer.io account is located (.US or .EU). Default: US //.region(.US) .build() ) MessagingPush.shared.didReceive(request, withContentHandler: contentHandler) } @objc(serviceExtensionTimeWillExpire) public func serviceExtensionTimeWillExpire() { MessagingPush.shared.serviceExtensionTimeWillExpire() } } ``` 9. In your `NotificationService.m` file, import the auto-generated header file—e.g. `NotificationServiceExtension-Swift.h`. You’ll also need to create an object class of `MyAppNotificationServicePushHandler` and call the functions in the code sample here. Now you can run your app on a physical device and send yourself a push notification with images and deep links to test your implementation. You’ll have to use a physical device because simulators can’t receive push notifications. ```obj-c #import #import "NotificationService.h" @interface NotificationService () @end @implementation NotificationService // Create object of class MyAppNotificationServicePushHandler MyAppNotificationServicePushHandler* nsHandlerObj = nil; // Initialize the object + (void)initialize { nsHandlerObj = [[MyAppNotificationServicePushHandler alloc] init]; } - (void)didReceiveNotificationRequest:(UNNotificationRequest*)request withContentHandler:(void (^)(UNNotificationContent* _Nonnull))contentHandler { [nsHandlerObj didReceive:request withContentHandler:contentHandler]; } - (void)serviceExtensionTimeWillExpire { [nsHandlerObj serviceExtensionTimeWillExpire]; } @end ``` #### FCM/Swift[](#FCM/Swift) 1. Open your `ios/Podfile` and add the Customer.io push dependency, highlighted here, to both your main target and `NotificationServiceExtension` target. ```ruby # Note: You may need to add this line, as required by FCM, to the top of your Podfile if you encounter errors during 'pod install' use_frameworks! :linkage => :static target 'YourApp' do # Look for the main app target. # Make all file modifications after this line: config = use_native_modules! # Add the following line to add the Customer.io native dependency: pod 'customerio-reactnative/fcm', :path => '../node_modules/customerio-reactnative' end # Next, copy and paste the code below to the bottom of your Podfile: target 'NotificationServiceExtension' do # Notice the '-richpush' in the line below. This line of code is different from what you added for your main target. pod 'customerio-reactnative-richpush/fcm', :path => '../node_modules/customerio-reactnative' end ``` 2. Open your terminal, go to your project path and install the pods. When complete, you should see `Pod installation complete!` ```bash pod install --project-directory=ios ``` 3. Update your `AppDelegate.swift` file to handle push notifications. You can copy the code sample on the right, but you may need to update imports and other things to fit your app. ```swift import UIKit import CioMessagingPushFCM import CioFirebaseWrapper import UserNotifications import Firebase import FirebaseMessaging @main class AppDelegateWithCioIntegration: CioAppDelegateWrapper {} class AppDelegate: NSObject, UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { ... // Configure Firebase FirebaseApp.configure() // Optional: Set FCM messaging delegate if you need it. The Customer.io SDK will automatically read FCM tokens Messaging.messaging().delegate = self // Initialize MessagingPushFCM module to automatically handle your app's push notifications that originate from Customer.io MessagingPushFCM.initialize(withConfig: MessagingPushConfigBuilder().build()) return true } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { // This is required for FCM. The Customer.io SDK does not make changes to other SDKs Messaging.messaging().apnsToken = deviceToken } } extension AppDelegate: UNUserNotificationCenterDelegate { // Optional: add this method if you want fine-grained control over presenting Notifications in foreground func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.banner, .list, .badge, .sound]) } } extension AppDelegate: MessagingDelegate { // Optional: add this method if you need access to `fcmToken` - Customer.io SDK will read this automatically func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { } } ``` 4. Add a notification service extension to call the appropriate Customer.io functions. This lets your app display rich push notifications, including images, etc. See [Deep Links](/integrations/sdk/react-native/push-notifications/deep-links/) if you want to support deep links from push notifications. ```swift import CioMessagingPushFCM import Foundation import UserNotifications class NotificationService: UNNotificationServiceExtension { override func didReceive( _ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void ) { MessagingPushFCM.initializeForExtension( withConfig: MessagingPushConfigBuilder(cdpApiKey: Env.cdpApiKey) // Optional: specify region where your Customer.io account is located (.US or .EU). Default: US //.region(.US) .build() ) MessagingPush.shared.didReceive(request, withContentHandler: contentHandler) } override func serviceExtensionTimeWillExpire() { // Called just before the extension is terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. MessagingPush.shared.serviceExtensionTimeWillExpire() } } ``` ### Sound in push notifications (iOS Only)[](#sound-in-push-notifications) When you send a push notification to iOS devices that uses our SDK, you can opt to send the *Default* system sound or no sound at all. If your audience’s phone is set to vibrate, or they’ve disabled sound permissions for your app, the *Default* setting will cause the device to vibrate rather than playing a sound. In most cases, you should use the *Default* sound setting to make sure your audience hears (or feels) your message. But, before you send sound, you should understand: 1. Your app needs permission from your users to play sounds. This is done by your app, not our SDKs. [Here’s an example from our iOS sample app](https://github.com/customerio/customerio-ios/blob/main/Apps/APN-UIKit/APN%20UIKit/Util/NotificationUtil.swift#L12-L13) showing how to request sound permissions. 2. iOS users can go into *System Settings* and disable sound permissions for your app. Enabling the *Default* setting doesn’t guarantee that your audience hears a sound when your message is delivered!  We don’t support custom sounds yet If you want to send a custom sound, you’ll need to handle it on your own, outside the SDK and use a custom payload when you set up your push notifications. ## Push icon (Android)[](#push-icon-android) You’ll set the icon that appears on normal push notifications as a part of your app manifest—`android/app/src/main/AndroidManifest.xml`. If your icon appears in the wrong size, or if you want to change the standard icon that appears with your push notifications, you’ll need to update your app’s manifest. ```xml ``` ## Prompt users to opt-into push notifications[](#prompt-users-to-opt-into-push-notifications) Your audience has to opt into push notifications. To display the native iOS and Android push notification permission prompt, you’ll use the `CustomerIO.showPromptForPushNotifications` method. You can configure push notifications to request authorization for sounds and badges as well (only on iOS). If a user opts into push notifications, the `CustomerIO.showPromptForPushNotifications` method will return `Granted`, otherwise it returns `Denied` as a `string`. If the user has not yet been asked to opt into notifications, the method will return `NotDetermined` (only for iOS). ```javascript var options = {"ios" : {"sound" : true, "badge" : true}} CustomerIO.showPromptForPushNotifications(options).then(status => { switch(status) { case "Granted": // Push permission is granted, your app can now receive push notifications break; case "Denied": // App is not authorized to receive push notifications // You might need to explain users why your app needs permission to receive push notifications break; case "NotDetermined": // Push permission status is not determined (Only for iOS) break; } }).catch(error => { // Failed to show push permission prompt console.log(error) }) ``` ### Get a user’s permission status[](#get-a-users-permission-status) To get a user’s current permission status, call the `CustomerIO.getPushPermissionStatus()` method. This returns a promise with the current status as a string. ```javascript CustomerIO.getPushPermissionStatus().then(status => { console.log("Push permission status is - " + status) }) ``` ### Optional: Remove `POST_NOTIFICATIONS` permission from Android apps[](#optional-remove-post_notifications-permission-from-android-apps) By default, the SDK includes the `POST_NOTIFICATIONS` permission which is [required by Android 13 to show notifications on Android device](https://developer.android.com/develop/ui/views/notifications/notification-permission). However, if you do not want to include the permission because don’t use notifications, or for any other reason, you can remove the permission by adding the following line to your `android/app/src/main/AndroidManifest.xml` file: ```xml ``` ## Fetch the current device token[](#fetch-the-current-device-token) You can fetch the currently stored device token using the `CustomerIO.pushMessaging.getRegisteredDeviceToken()` method. This method returns an APN/FCM token in a promise as a string. ```javascript let token = await CustomerIO.pushMessaging.getRegisteredDeviceToken() if (token) { // Use the token as required in your app for example save in a state setDeviceToken(token); } ``` ## Test your implementation[](#rich-push-payloads) After you set up rich push, you should test your implementation. Below, we show the payload structure we use for iOS and Android. In general, you can use our regular rich push editor; it’s set up to send messages using the JSON structure we outline below. If you want to fashion your own payload, you can use our [custom payload](/journeys/push-custom-payloads/#getting-started-with-custom-payloads). [![the rich push editor](https://customer.io/images/push-preview.png)](#4e089ac68a22d5b994db09266a531737-lightbox) iOS APNs payload #### iOS APNs payload[](#iOS APNs payload) ```json { "aps": { // basic iOS message and options go here "mutable-content": 1, "alert": { "title": "string", //(optional) The title of the notification. "body": "string" //(optional) The message you want to send. } }, "CIO": { "push": { "link": "string", //generally a deep link, i.e. my-app:://... "image": "string" //HTTPS URL of your image, including file extension } } } ``` * CIO object Contains options supported by the Customer.io SDK. * push object Required Describes push notification options supported by the CIO SDK. * image string The URL of an HTTPS image that you want to use for your message. * link string A deep link (to a page in your app), or a link to a web page. * aps object A push payload intended for an iOS device. * alert string A simple alert message. * body string The body of your push notification. * launch-image string The name of the launch image file you want to display. When a user launches your app, they’ll see this image or storyboard file rather than your app’s normal launch image. * loc-args array of \[ strings \] An array of replacement value strings for variables in your message text. Each %@ character in the loc-key is replaced by a value from this array, in the order they appear in the message body. * loc-key string The key for a localized message string in your app’s Localizable.strings file. * subtitle string Additional information that explains the purpose of the notification. * subtitle-loc-args array of \[ strings \] An array of replacement value strings for variables in your subtitle string. Each %@ character in the subtitle-loc-key is replaced by a value from this array, in the order they appear in the subtitle string. * subtitle-loc-key string The key for a localized subtitle string in your app’s Localizable.strings file. * title string The title of your push notification. * title-loc-args array of \[ strings \] An array of replacement value strings for variables in your title string. Each %@ character in the title-loc-key is replaced by a value from this array, in the order they appear in the title string. * title-loc-key string The key for a localized title string in your app’s Localizable.strings files. * badge integer The number you want to display on your app’s icon. Set to 0 to remove the current badge, if any. * category string The notification’s type. This string must correspond to the identifier of one of the `UNNotificationCategory` objects you register at launch time. * content-available integer The background notification flag. Use `1` without an `alert` to perform a silent update. `0` indicates a normal push notification. * interruption-level string Indicates the importance and delivery timing of a notification. Accepted values:`passive`,`active`,`time-sensitive`,`critical` * mutable-content integer If you use the Customer.io SDK, you *must* set this value to `1` to support images and “delivered” metrics from your push notifications. When the value is 1, your notification is passed to your notification service app extension before delivery. Use your extension to modify the notification’s content. * relevance-score number A number between 0 and 1. The highest score is considered the “most relevant” and is featured in the notification summary. * sound string The name of a sound file in your app’s main bundle or in the Library/Sounds folder of your app’s container directory. Use “default” to play the system sound. For critical alerts, you’ll pass an object instead. * critical integer 1 indicates critical. 0 is not critical. * name string The name of a sound file in your app’s main bundle or in the Library/Sounds folder of your app’s container directory. Use “default” to play the system sound. * volume number The volume for a critical alert between 0 and 1, where 0 is silent and 1 is full volume. * target-content-id string The identifier of the window brought forward. * thread-id string An identifier to group related notifications. iOS FCM payload #### iOS FCM payload[](#iOS FCM payload) ```json { "message": { "apns": { "payload": { "aps": { // basic iOS message and options go here "mutable-content": 1, "alert": { "title": "string", //(optional) The title of the notification. "body": "string" //(optional) The message you want to send. } }, "CIO": { "push": { "link": "string", //generally a deep link, i.e. my-app://... or https://yourwebsite.com/... "image": "string" //HTTPS URL of your image, including file extension } } }, "headers": { // (optional) headers to send to the Apple Push Notification Service. "apns-priority": 10 } } } } ``` * message object Required The base object for all FCM payloads. * apns object Required Defines a payload for iOS devices sent through Firebase Cloud Messaging (FCM). * headers object Headers defined by [Apple’s payload reference](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns) that you want to pass through FCM. * payload object Required Contains a push payload. * CIO object Contains properties interpreted by the Customer.io iOS SDK. * push object Required A push payload for the iOS SDK. * body string The body of your push notification. * image string The URL of an HTTPS image that you want to use for your message. * link string A deep link (to a page in your app), or a link to a web page. * title string The title of your push notification. * aps object A push payload intended for an iOS device. * alert string A simple alert message. * body string The body of your push notification. * launch-image string The name of the launch image file you want to display. When a user launches your app, they’ll see this image or storyboard file rather than your app’s normal launch image. * loc-args array of \[ strings \] An array of replacement value strings for variables in your message text. Each %@ character in the loc-key is replaced by a value from this array, in the order they appear in the message body. * loc-key string The key for a localized message string in your app’s Localizable.strings file. * subtitle string Additional information that explains the purpose of the notification. * subtitle-loc-args array of \[ strings \] An array of replacement value strings for variables in your subtitle string. Each %@ character in the subtitle-loc-key is replaced by a value from this array, in the order they appear in the subtitle string. * subtitle-loc-key string The key for a localized subtitle string in your app’s Localizable.strings file. * title string The title of your push notification. * title-loc-args array of \[ strings \] An array of replacement value strings for variables in your title string. Each %@ character in the title-loc-key is replaced by a value from this array, in the order they appear in the title string. * title-loc-key string The key for a localized title string in your app’s Localizable.strings files. * badge integer The number you want to display on your app’s icon. Set to 0 to remove the current badge, if any. * category string The notification’s type. This string must correspond to the identifier of one of the `UNNotificationCategory` objects you register at launch time. * content-available integer The background notification flag. Use `1` without an `alert` to perform a silent update. `0` indicates a normal push notification. * interruption-level string Indicates the importance and delivery timing of a notification. Accepted values:`passive`,`active`,`time-sensitive`,`critical` * mutable-content integer If you use the Customer.io SDK, you *must* set this value to `1` to support images and “delivered” metrics from your push notifications. When the value is 1, your notification is passed to your notification service app extension before delivery. Use your extension to modify the notification’s content. * relevance-score number A number between 0 and 1. The highest score is considered the “most relevant” and is featured in the notification summary. * sound string The name of a sound file in your app’s main bundle or in the Library/Sounds folder of your app’s container directory. Use “default” to play the system sound. For critical alerts, you’ll pass an object instead. * critical integer 1 indicates critical. 0 is not critical. * name string The name of a sound file in your app’s main bundle or in the Library/Sounds folder of your app’s container directory. Use “default” to play the system sound. * volume number The volume for a critical alert between 0 and 1, where 0 is silent and 1 is full volume. * target-content-id string The identifier of the window brought forward. * thread-id string An identifier to group related notifications. * *Custom key-value pairs\** any type Additional properties that you've set up your app to interpret outside of the Customer.io SDK. Android payload #### Android payload[](#Android payload) ```json { "message": { "data": { "title": "string", //(optional) The title of the notification. "body": "string", //The message you want to send. "image": "string", //https URL to an image you want to include in the notification "link": "string" //Deep link in the format remote-habits://deep?message=hello&message2=world } } } ``` * message Required The parent object for all push payloads. * android object Contains properties that are **not** interpreted by the SDK but are defined by FCM. You need to write your own code to handle these Android push features. * notification object Properties supported specifically by Android on FCM. * body\_loc\_arg string Variable string values used in place of the format specifiers in `body_loc_key` to localize the body text to the user’s current localization. See Formatting and Styling for more information. * body\_loc\_key string The key to the body string in the app’s string resources that you want to use to localize the body text to the user’s current localization. See [String Resources](https://developer.android.com/guide/topics/resources/string-resource/) for more information. * click\_action string The action that occurs when a user taps on the notification. Launches an activity with a matching intent filter when a person taps the notification. * color string The notification’s icon color in `#rrggbb` format. * icon string Sets the notification icon to `myicon` for drawable resource `myicon`. If you don’t send this key, FCM displays the launcher icon from your app manifest. * sound string The sound that plays when the device receives the notification. Supports `"default"` or the filename of a sound resource bundled in your app. Sound files must reside in `/res/raw/`. * tag string Identifier to replace existing notifications in the notification drawer. If empty, each request creates a new notification. If you specify a tag, and a notification with the same tag is already being shown, the new notification replaces the existing one in the notification drawer. * title\_loc\_arg string Variable string values used in place of the format specifiers in `title_loc_key` to localize the title text to the user’s current localization. See Formatting and Styling for more information. * title\_loc\_key string The key to the title string in the app’s string resources that you want to use to localize the title text to the user’s current localization. See [String Resources](https://developer.android.com/guide/topics/resources/string-resource/) for more information. * data object Required Contains all properties interpreted by the SDK. * body string The body of your push notification. * image string The URL of an HTTPS image that you want to use for your message. * link string A deep link (to a page in your app), or a link to a web page. * title string The title of your push notification. * android object Contains properties that are **not** interpreted by the SDK but are defined by FCM. You need to write your own code to handle these Android push features. * notification object Properties supported specifically by Android on FCM. * body\_loc\_arg string Variable string values used in place of the format specifiers in `body_loc_key` to localize the body text to the user’s current localization. See Formatting and Styling for more information. * body\_loc\_key string The key to the body string in the app’s string resources that you want to use to localize the body text to the user’s current localization. See [String Resources](https://developer.android.com/guide/topics/resources/string-resource/) for more information. * click\_action string The action that occurs when a user taps on the notification. Launches an activity with a matching intent filter when a person taps the notification. * color string The notification’s icon color in `#rrggbb` format. * icon string Sets the notification icon to `myicon` for drawable resource `myicon`. If you don’t send this key, FCM displays the launcher icon from your app manifest. * sound string The sound that plays when the device receives the notification. Supports `"default"` or the filename of a sound resource bundled in your app. Sound files must reside in `/res/raw/`. * tag string Identifier to replace existing notifications in the notification drawer. If empty, each request creates a new notification. If you specify a tag, and a notification with the same tag is already being shown, the new notification replaces the existing one in the notification drawer. * title\_loc\_arg string Variable string values used in place of the format specifiers in `title_loc_key` to localize the title text to the user’s current localization. See Formatting and Styling for more information. * title\_loc\_key string The key to the title string in the app’s string resources that you want to use to localize the title text to the user’s current localization. See [String Resources](https://developer.android.com/guide/topics/resources/string-resource/) for more information. * data object Contains the `link` property (interpreted by the SDK) and additional properties that you want to pass to your app. * link string A deep link (to a page in your app), or a link to a web page. * notification object Required Contains properties interpreted by the SDK except for the `link`. * body string The body of your push notification. * image string The URL of an HTTPS image that you want to use for your message. * title string The title of your push notification. --- ## Push notifications > Push metrics **Source:** /integrations/sdk/react-native/push-notifications/push-metrics # Capture Push Metrics ## Automatic push handling[](#automatic-push-handling) Customer.io supports device-side metrics that help you determine the efficacy of your push notifications: `delivered` when a push notification is received by the app and `opened` when a push notification is clicked. The SDK automatically tracks `opened` and `delivered` events for push notifications originating from Customer.io after you configure your app to receive [push notifications](/integrations/sdk/react-native/push-notifications/push/). You don’t have to add any code to track `opened` push metrics or launch deep links.  Do you use multiple push services in your app? The Customer.io SDK only handles push notifications that originate from Customer.io. Push notifications that were sent from other push services or displayed locally on device are not handled by the Customer.io SDK. You must add custom handling logic to your app to handle those push events. ### Choose whether to show push while your app is in the foreground[](#show-push-app-foreground) If your app is in the foreground and the device receives a Customer.io push notification, your app gets to choose whether or not to display the push. You can configure this behavior by adding the following configuration to the class that you created as a part of our [push notification setup instructions](/integrations/sdk/react-native/push-notifications/push/) in your `AppDelegate.swift` file. ```swift // In your AppDelegate.swift @main class AppDelegateWithCioIntegration: CioAppDelegateWrapper {} class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Initialize with foreground push display option MessagingPushAPN.initialize( withConfig: MessagingPushConfigBuilder().showPushAppInForeground(true).build() ) return true } } ``` If the push did not come from Customer.io, you’ll need to [perform custom handling](#handle-push-received-foreground) to determine whether to display the push or not. ### Custom handling when users click a push[](#handle-push-click) You might need to perform custom handling when a user clicks a push notification—like you want to process custom fields in your push notification payload. For now, the React Native SDK does not provide callbacks when your audience clicks a push notification. But you can use one of the many popular React Native push notification SDKs to receive a callback. For example, the code below receives callbacks when users click a push using `react-native-push-notification`. Be sure to follow the documentation for the push notification SDK you choose to use to receive callbacks with. ```js import { Notifications } from 'react-native-notifications'; Notifications.events().registerNotificationOpened((notification: Notification, completion) => { // Process custom data attached to payload, if you need: let pushPayload = notification.payload; // Important: When you're done processing the push notification, you're required to call completion(). // Even if you do not process a push, this is still a requirement. completion(); }); ```  Do you use deep links? If you’re performing custom push click handling on push notifications originating from Customer.io, we recommend that you don’t launch a deep link URL yourself. Instead, let our SDK launch deep links to avoid unexpected behaviors. ### Custom handling when getting a push while the app is foregrounded[](#handle-push-received-foreground) If your app is in the foreground and you get a push notification, your app gets to choose whether or not to display the push. For push notifications originating from Customer.io, [your SDK configuration determines if you show the notification](#show-push-app-foreground). But you can add custom logic to your app when this kind of thing happens. For now, the React Native SDK does not provide callbacks when a push notification is received and your app is in the foreground. But you can use one of the many popular React Native push notification SDKs to receive a callback. For example, the code below receives a callback using `react-native-push-notification`. Be sure to follow the documentation for the push notification SDK you choose to use to receive callbacks with. ```js import { Notifications } from 'react-native-notifications'; Notifications.events().registerNotificationReceivedForeground( (notification: Notification, completion) => { // Important: When you're done processing the push notification, you must call completion(). // Even if you do not process a push, you must still call completion(). completion({ alert: true, sound: true, badge: true }); // If the push notification originated from Customer.io, the value returned in the `completion` is ignored by the SDK. // Use the SDK's push configuration options instead. }); ``` ## Manually record push metrics using Javascript methods[](#metrics-javascript)  Avoid duplicate push metrics If you manually track your own metrics, you should [disable automatic push tracking](#disabling-automatic-push-tracking) to avoid duplicate push metrics.  Known issue tracking `opened` push metrics in app `killed` state When manually tracking push metrics using Javascript methods, `opened` push metrics are *not tracked* when the app is in `killed` or `closed` state. This is a known behavior and it’s recommended to instead use the automatic push tracking feature. To monitor the `delivered` push metrics of a received push notification, use the `CustomerIO.pushMessaging.trackNotificationReceived()` method. ```javascript CustomerIO.pushMessaging.trackNotificationReceived() ``` To track `opened` push metrics, use the `CustomerIO.pushMessaging.trackNotificationResponseReceived()` method. ```javascript CustomerIO.pushMessaging.trackNotificationResponseReceived() ``` The method that you use to retrieve the `` value depends on API of the SDK that you are using to receive push notifications from. Here is a code snippet as an example from `expo-notifications`: ```javascript // Listener called when a push notification is received Notifications.addNotificationReceivedListener(notification => { ... // Fetch Customer.io payload from the push notification const payload = notification.request.trigger.payload CustomerIO.pushMessaging.trackNotificationReceived(payload) ... }); // Receives response when user interacts with the push notification Notifications.addNotificationResponseReceivedListener(response => { ... // Fetch Customer.io payload from the push notification response const payload = response.notification.request.trigger.payload CustomerIO.pushMessaging.trackNotificationResponseReceived(payload) ... }); ``` ## Disabling automatic push tracking[](#disabling-automatic-push-tracking) After you set up [push notifications](/integrations/sdk/react-native/push-notifications/push/), update your `AppDelegate.swift` file to disable automatic push notification tracking: ```swift // In your AppDelegate.swift func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Initialize with auto-tracking disabled MessagingPushAPN.initialize( withConfig: MessagingPushConfigBuilder().autoTrackPushEvents(false).build() ) return true } ``` --- ## Push notifications > Push notification channel **Source:** /integrations/sdk/react-native/push-notifications/push-notification-channel # Android push notification channels 🎉New in v4.5.0 Starting in Android 8.0, you can set up “notification channels,” which categorize notifications for your Android app. Every notification now belongs to a channel and the channel determines the behavior of notifications—whether they play sounds, appear as heads-up notifications, and so on. Channels also give users control over which channels they want to see notifications from. For example, if you had a news app, you might have different channels for sports, entertainment, and breaking news, giving users the ability to pick the channels they care about. Today, Customer.io supports **a single channel per app**, and it has three settings, listed in the table below. You can customize your channel when you first set up the Customer.io SDK, but you cannot change the channel ID or importance level after you’ve created a channel. You can only change the channel name. [Learn more from the official Android developer docs](https://developer.android.com/develop/ui/views/notifications/channels). Channels are created on the audience’s side when they receive their first push from Customer.io. Users can see your channel in their device settings. Channel setting Default Description Channel ID `[your package name]` The ID of the channel. Channel name `[your app name] Notifications` The name of the channel. Importance `3` The importance of the channel. Acceptable values are `0` (min), `1` (low), `2` (medium), `3` (default/high), and `4` (urgent). See [the Android developer documentation](https://developer.android.com/develop/ui/views/notifications/channels#importance) for more about the behavior of each importance level. ## Channel configuration[](#channel-configuration) When you first integrate with the Customer.io SDK, you can set up your Android channel. Remember, after you’ve released a version of your app with channel settings, you can only change the channel name. Changes to other settings have no effect. You’ll customize your channel in your app’s manifest. ```xml ``` ### What channel settings can I change?[](#what-channel-settings-can-i-change) When you first set up the Customer.io React-Native SDK, you can customize your channel. But after you release a version of your app with the Customer.io SDK, you cannot change the channel ID or importance level. After that, you can only change the channel name. (This is a limitation imposed by Android, not Customer.io.) If you released your app with a version of the Customer.io React-Native SDK prior to 4.5.0, you can delete your old channel and create a new one with completely new settings per [Android’s developer documentation](https://developer.android.com/develop/ui/views/notifications/channels#DeleteChannel). The chart below shows what channel settings you can or can’t change: ## Delete a channel[](#delete-a-channel) If you’ve released a version of your app with the Customer.io SDK earlier than v4.5.0, you can delete your old channel and create a new one with completely new settings per [Android’s developer documentation](https://developer.android.com/develop/ui/views/notifications/channels#DeleteChannel). ```kotlin val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val id: String = context.packageName notificationManager.deleteNotificationChannel(id) ``` --- ## Tracking > Anonymous activity **Source:** /integrations/sdk/react-native/tracking/anonymous-activity # Anonymous activity Before you identify a person, calls you make to the SDK are associated with an `anonymousId`. When you identify that person, we reconcile their anonymous activity with the identified person. In Customer.io, you’ll see anonymous activity in the *Activity Log*, but we don’t surface anonymous [profilesAn instance of a person. Generally, a person is synonymous with their profile; there should be a one-to-one relationship between a real person and their profile in Customer.io. You reference a person’s profile attributes in liquid using `customer`—e.g. `{{customer.email}}`.](/merge-people) in Customer.io. You won’t be able to find an “anonymous person” in your workspace, and an anonymous person can’t trigger campaigns or get messages (including push notifications) from Customer.io. When you identify a person, we merge anonymous activity with the identified person. And then the identified person’s previously-anonymous activity *can* trigger campaigns and cause your audience to receive messages. For example, imagine that you have an ecommerce app, and you want to message people who view a specific product. An anonymous user looks at the product in question, goes to a different page, and then logs into your app. When they log in, we merge their anonymous activity including their `screen` view. This triggers the campaign you set up for people who visited the product page. --- ## Tracking > Identify **Source:** /integrations/sdk/react-native/tracking/identify # Identify people Use `CustomerIO.identify()` to identify a person. You need to identify a mobile user before you can send them messages or track events for things they do in your app. ## Identify a person[](#identify) Identifying a person: 1. Adds or updates the person in your workspace. This is basically the same as an [`identify` call to our server-side API](/api/#operation/identify). 2. Saves the person’s information on the device. Future calls to the SDK reference the identified person. For example, after you identify a person, any events that you track are automatically associated with that person. 3. Associates the current device token with the the person. You can only identify one customer at a time. The SDK “remembers” the most recently-identified customer. If you identify person A, and then call the identify function for person B, the SDK “forgets” person A and assumes that person B is the current app user. You can also [stop identifying a person](#clearIdentify), which you might do when someone logs off or stops using your app for a significant period of time. An identify request takes two parameters: * **userId** (Required): The unique value representing a person—an ID, email address, or the [cio\_idAn identifier for a person that is automatically generated by Customer.io and cannot be changed. This identifier provides a complete, unbroken record of a person across changes to their other identifiers (id, email, etc).](/identifying-people/#cio_id) * **traits** (Optional): An object containing [attributesA key-value pair that you associate with a person or an object—like a person’s name, the date they were created in your workspace, or a company’s billing date etc. Use attributes to target people and personalize messages.](/journeys/attributes/) that you want to add to, or update on, a person ```javascript import { CustomerIO } from "customerio-reactnative"; // Call this method whenever you are ready to identify a user CustomerIO.identify({ userId: "user_id", traits: { first_name: "user_name", email: "email_identifier", }, }); ``` ### Update a person’s attributes[](#update-person) You store information about a person in Customer.io as [attributesA key-value pair that you associate with a person or an object—like a person’s name, the date they were created in your workspace, or a company’s billing date etc. Use attributes to target people and personalize messages.](/journeys/attributes/). When you call the `CustomerIO.identify()` function, you can update a person’s attributes on the server-side. If a person is already identified, and then updates their preferences, provides additional information about themselves, or performs other attribute-changing actions, you can update their attributes with `setProfileAttributes`. You only need to pass the attributes that you want to create or modify to `setProfileAttributes`. For example, if you identify a new person with the attribute `["first_name": "Dana"]`, and then you call `CustomerIO.setProfileAttributes = ["favorite_food": "pizza"]` after that, the person’s `first_name` attribute will still be `Dana`. ```javascript const profileAttributes = { favouriteFood: "Pizza", favouriteDrink: "Mango Shake" }; CustomerIO.setProfileAttributes(profileAttributes) ``` ### Device attributes[](#device-attributes) By default (if you don’t set `.autoTrackDeviceAttributes(false)` in your config), the SDK automatically collects a series of [attributesA key-value pair that you associate with a person or an object—like a person’s name, the date they were created in your workspace, or a company’s billing date etc. Use attributes to target people and personalize messages.](/journeys/attributes/) for each device. You can use these attributes in [segmentsA group of people who match a series of conditions. People enter and exit the segment automatically when they match or stop matching conditions.](/journeys/data-driven-segments/) and other campaign workflow conditions to target the device owner, just like you would use a person’s other attributes. You cannot, however, use device attributes to personalize messages with [liquidA syntax that supports variables, letting you personalize messages for your audience. For example, if you want to reference a person’s first name, you might use the variable `{{customer.first_name}}`.](/using-liquid) yet. Along with these attributes, we automatically set a `last_used` timestamp for each device indicating when the device owner was last identified, and the `last_status` of a push notification you sent to the device. You can also set your own custom device attributes. You’ll see a person’s devices and each device’s attributes when you go to **Journeys > People > Select a person**, and click **Devices**. [![device attributes on a person's profile](https://customer.io/images/device-attributes.png)](#db82cfb11dbec1c42a9f938183dcedbd-lightbox)  Your integration shows device attributes in the `context` object When you inspect calls from the SDK (in your integration’s data inAn integration that feeds data *into* Customer.io. tab), you’ll see device information in the `context` object. We flatten the device attributes that you send into your workspace, so that they’re easier to use in [segmentsA segment is a group of people in your workspace. Use segments to trigger campaigns, track membership over time, or fine-tune your audience. There are two types of segments: data-driven and manual. Data-driven segments automatically update when people start or stop matching criteria. Manual segments are static.](/journeys/segments/). For example, `context.network.cellular` becomes `network_cellular`. * id string Required The device token. * attributes object Attributes that you can reference to segment your audience—like a person’s attributes, but specific to a device. These can be either the attributes defined below or custom key-value attributes. * \_last\_status string The delivery status of the last message sent to the device—sent, bounced, or suppressed. An empty string indicates that that the device hasn’t received a push yet. Accepted values:,`bounced`,`sent`,`suppressed` * app\_version string The version of your app that a customer uses. You might target app versions to let people know when they need to update, or expose them to new features when they do. * cio\_sdk\_version string The version of the Customer.io SDK in the app. * device\_locale string The four-letter [IETF language code](/localization/#supported-languages) for the device. For example, `en-MX` (indicating an app in Spanish formatted for a user in Mexico) or `es-ES` (indicating an app in Spanish formatted for a user in Spain). * device\_model string The model of the device a person uses. * device\_os string The operating system, including the version, on the device. * network\_bluetooth boolean If `true`, the device’s bluetooth connection is on. * network\_cellular boolean If `true`, the device’s cellular connection is on. * network\_wifi boolean If `true`, the device’s WiFi connection is on. * push\_enabled string If `"true"`, the device is opted-in and can receive push notifications. Accepted values:`true`,`false` * screen\_height integer The height of the device’s screen in pixels. * screen\_width integer The width of the device’s screen in pixels. * timezone string The time zone of the device. * *Custom Device Attributes\** string Custom properties that you want to associate with the device. * last\_used integer  (unix timestamp) The `timestamp` when you last identified this device. If you don’t pass a timestamp when you add or update a device, we use the time of the request itself. Our SDKs identify a device when a person launches their app. * platform string Required The device/messaging platform. Accepted values:`ios`,`android` #### Set custom device attributes[](#update-device) You can also set custom device attributes with the `setDeviceAttributes` method. You might do this to save app preferences, time zone, or other custom values specific to the device. Like profile attributes, you can pass nested JSON to device attributes. However, before you set custom device attributes, consider whether the attribute is specific to the `device` or if it applies to the person broadly. Device tokens are ephemeral—they can change based on user behavior, like when a person uninstalls and reinstalls your app. If you want an attribute to persist beyond the life of the device, you should [apply it to the person](#update-person) rather than the device. ```javascript const setDeviceAttributes = () => { const deviceAttributes = { type : "primary_device", parentObject : { childProperty : "someValue", }, }; CustomerIO.setDeviceAttributes(deviceAttributes) } ``` #### Manually add device to profile[](#add-device) In the standard flow, identifying a person automatically associates the token with the identified person in your workspace. If you need to manually add or update the device elsewhere in your code, call the method `CustomerIO.registerDeviceToken(token)`. ```javascript const registerDevice = () => { // Customer.io expects a valid token to send push notifications // to the user. const token = 'token' CustomerIO.registerDeviceToken(token) } ``` ## Stop identifying a person[](#clearIdentify) When a person logs out, or does something else to tell you that they no longer want to be tracked, you should stop identifying them. Use `clearIdentify()` to stop identifying the previously identified person (if there was one). ```javascript CustomerIO.clearIdentify() ``` ### Identify a different person[](#identify-a-different-person) If you want to identify a new person—like when someone switches profiles on a streaming app, etc—you can simply call `identify()` for the new person. The new person then becomes the currently-identified person, with whom all new information—messages, events, etc—is associated. ```javascript CustomerIO.identify( userId: "[email protected]", traits: { first_name: "New", last_name: "Person" })  ``` --- ## Tracking > Lifecycle events **Source:** /integrations/sdk/react-native/tracking/lifecycle-events # Mobile Lifecycle events By default, our Android SDK automatically tracks events that represent the lifecycle of your app and your users experiences with it. By default, we track the following lifecycle events: * **Application Installed**: A user installed your app. * **Application Updated**: A user updated your app. * **Application Opened**: A user opened your app. * **Application Foregrounded**: A user switched back to your app. * **Application Backgrounded**: A user backgrounded your app or switched to another app. You might also want to send your own lifecycle events, like `Application Crashed` or `Application Updated`. You can do this using the `track` method. You’ll find a list of properties for these events—both the ones we track automatically and other events you might send yourself—in our [Mobile App Lifecycle Event specification](/integrations/api/cdp/#section/Semantic-events-for-data-out-integrations/mobile-application-lifecycle-event-schemas). ## Lifecycle event examples[](#lifecycle-event-examples) A lifecycle event is basically a `track` call that the SDK makes automatically for you. When you look at your data in Customer.io, you’ll see lifecycle events as `track` calls, where the event `properties` are specific to the name of the event. For example, the `Application Installed` event includes the app `version` and `build` properties. ```json { "userId": "[email protected]", "type": "track", "event": "Application Installed", "properties": { "version": "3.2.1", "build": "247" } } ``` ## Sending custom lifecycle events[](#sending-custom-lifecycle-events) You can send your own lifecycle events using the `track` call. However, whenever you send lifecycle events, you should use the *Application EventName* convention that we use for our default lifecycle events. These [semantic event](/integrations/data-in/semantic-events/) names and properties represent [a standard](/integrations/data-in/semantic-events/mobile-app/) that we use across Customer.io and our downstream destinations. Adhering to this standard ensures that your events automatically map to the correct event types in Customer.io and any other services you send your data to. If you opt out of automatic lifecycle events, you can send your own `track` calls for these events. Or, for events we can’t track automatically, you might be able to use a webhook or a callback to collect crash events. For example, you might want to send a `track` call for `Application Crashed` when your app crashes or `Application Updated` when people update your app. ```javascript CustomerIO.track("Application Crashed", { url: "/page/in/app" }); ``` ## Disable lifecycle events[](#disable-lifecycle-events) We track lifecycle events by default. You can disable this behavior by passing the `trackApplicationLifecycleEvents` option in the `CioConfig` object when you initialize the SDK. ```javascript import { CioLogLevel, CioRegion, CustomerIO, CioConfig } from 'customerio-reactnative'; const config: CioConfig = { cdpApiKey: 'cdp_api_key', // Mandatory migrationSiteId: 'site_id', // For migration region: CioRegion.US, trackApplicationLifecycleEvents: false, inApp: { siteId: 'site_id', // this removes the use of enableInApp and simplifies in-app configuration } }; CustomerIO.initialize(config) ``` --- ## Tracking > Screen events **Source:** /integrations/sdk/react-native/tracking/screen-events # Screen tracking Screen views are events that record the pages that your audience visits in your app. They have a `type` property set to `screen`, and a `name` representing the title of the screen or page that a person visited in your app. Screen view events let you trigger [campaignsCampaigns are automated workflows you set up to send people messages and perform other actions when they meet your criteria.](/journeys/campaigns-in-customerio/) or add people to [segmentsA group of people who match a series of conditions. People enter and exit the segment automatically when they match or stop matching conditions.](/journeys/data-driven-segments/) based on the parts of your app your audience uses. Screen view events also update your audience’s “Last Visited” attribute, which can help you track how recently people used your app. ## Enable automatic screen tracking[](#auto-screenview) We’ve provided some example code below using [React Navigation](https://reactnavigation.org/docs/screen-tracking/) for automatic screen tracking. This example requires `@react-navigation/native` and `@react-navigation/native-stack` to create a navigation container in `App.js` If you want to send more data with screen events, or you don’t want to send events for every individual screen that people view in your app, you [send screen events manually](#manual-screenview). ```javascript import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { useRef } from 'react'; const Stack = createNativeStackNavigator(); export default function App() { const navigationRef = useNavigationContainerRef(); const routeNameRef = useRef(); return ( { routeNameRef.current = navigationRef.getCurrentRoute().name; }} onStateChange={async () => { const previousRouteName = routeNameRef.current; const currentRouteName = navigationRef.getCurrentRoute().name; if (previousRouteName !== currentRouteName) { CustomerIO.screen(currentRouteName) } routeNameRef.current = currentRouteName; }} > ); }; ``` ### Screenview settings for in-app messages[](#screenview-settings-for-in-app-messages) Customer.io uses `screen` events to determine where users are in your app so you can target them with in-app messages on specific screens. By default, the SDK sends `screen` events to Customer.io’s backend servers. But, if you don’t use `screen` events to track user activity, segment your audience, or to trigger campaigns, these events might constitute unnecessary traffic and event history. If you don’t use `screen` events for anything other than in-app notifications, you can set the `ScreenViewUse` parameter to `ScreenView.InApp`. This setting stops the SDK from sending `screen` events back to Customer.io but still allows the SDK to use `screen` events for in-app messages, so you can target in-app messages to the right screen(s) without sending event traffic into Customer.io! ```javascript import { CioLogLevel, CioRegion, CustomerIO, CioConfig } from 'customerio-reactnative'; const App = () => { useEffect(() => { const config: CioConfig = { cdpApiKey: 'CDP API Key', // Mandatory region: CioRegion.US, screenViewUse: ScreenView.All trackApplicationLifecycleEvents: true, inApp: { siteId: 'site_id', } }; CustomerIO.initialize(config) }, []) } ``` ## Send your own screen events[](#manual-screenview) Screen events use the `.screen` method. Like other event types, you can add a `data` object containing additional information about the event or the currently-identified person. ```javascript CustomerIO.screen("screen-name", {"property": "value"}) ``` --- ## Tracking > Track events **Source:** /integrations/sdk/react-native/tracking/track-events # Track events Events represent things people do in your app so that you can track your audience’s activity and metrics. Use events to segment your audience, trigger campaigns, and capture usage metrics in your app. ## Track an event[](#track-an-event) The `track` method helps you send events representing your audience’s activities to Customer.io. When you send events, you can include event `properties`—information about the person or the event that they performed. In Customer.io, you can use events to trigger campaigns and broadcasts. Those campaigns might send someone a push notification or manipulate information associated with the person in your workspace. Events include the following: * `name`: the name of the event. Most event-based searches in Customer.io hinge on the name, so make sure that you provide an event name that will make sense to other members of your team. * `properties` (Optional): Additional information that you might want to reference in a message. You can reference data attributes in messages and other campaign actionsA block in a campaign workflow—like a message, delay, or attribute change. using [liquidA syntax that supports variables, letting you personalize messages for your audience. For example, if you want to reference a person’s first name, you might use the variable `{{customer.first_name}}`.](/using-liquid) in the format `{{event.}}`. ```javascript import { CustomerIO } from "customerio-reactnative"; CustomerIO.track("event_name", { propertyName: propertyValue }); ```  Perform downstream actions with semantic events Some downstream actions don’t neatly map to our simple `identify`, `track`, and other calls. For these, we use “semantic events,” events that have a special meaning in Customer.io and your destinations. See [Semantic Events](#semantic-events) for more information. ### Anonymous activity[](#anonymous-activity) If you send a `track` call before you `identify` a person, we’ll attribute the event to an `anonymousId`. When you identify the person, we’ll reconcile their anonymous activity with the identified person. When we apply anonymous events to an identified person, the previously anonymous activity becomes eligible to trigger campaigns in Customer.io. ## Semantic Events[](#semantic-events) Some actions don’t map cleanly to our simple `identify`, `track`, and other calls. For these, we use “semantic events,” events that have a special meaning in Customer.io and your destinations. These are especially important in Customer.io for destructive operations like deleting a person. When you send an event with a semantic `event` name, we’ll perform the appropriate action. For example, if a person decides to leave your service, you might delete them from your workspace. In Customer.io, you’ll do that with a `Delete Person` event. ```javascript CustomerIO.track("User Deleted) ``` --- ## Whats new > 4.3 upgrade **Source:** /integrations/sdk/react-native/whats-new/4.3-upgrade # 4.x -> 4.3 Version 4.3 of the Customer.io React Native SDK introduces a new `CioAppDelegateWrapper` pattern for iOS that simplifies push notification setup and eliminates the need for method swizzling. ## Key Changes[](#key-changes) The primary change in version 4.3 is the introduction of the wrapper pattern for handling push notifications on iOS. This change: * **Eliminates method swizzling**: No more automatic method replacement * **Simplifies setup**: Less boilerplate code required * **Improves reliability**: More predictable behavior See the instructions below to update your app depending on whether you send push notifications with APN or FCM and whether you use UIKit or SwiftUI. ## Update with APN (Apple Push Notification service)[](#update-appdelegate-apn) ### UIKit[](#uikit-apn) Update your `AppDelegate.swift` file to use the new `CioAppDelegateWrapper` pattern. See the *Before* sample to see what needs to change and the *After* sample to see the new pattern. Before (4.x) #### Before (4.x)[](#Before \(4.x\)) ```swift import UIKit import CioMessagingPushAPN import UserNotifications @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Initialize push MessagingPushAPN.initialize(withConfig: MessagingPushConfigBuilder().build()) // Register for push notifications UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in if granted { DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() } } } return true } // Manual push handling methods func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { MessagingPush.shared.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { MessagingPush.shared.application(application, didFailToRegisterForRemoteNotificationsWithError: error) } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { MessagingPush.shared.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler) } } ``` After (4.3) #### After (4.3)[](#After \(4.3\)) ```swift import UIKit import CioMessagingPushAPN import UserNotifications @main class AppDelegateWithCioIntegration: CioAppDelegateWrapper {} class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Initialize push with wrapper - handles all push methods automatically MessagingPushAPN.initialize(withConfig: MessagingPushConfigBuilder().build()) // Register for push notifications // You can move this line to any part of your app. It's not critical to call it in this method. UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in if granted { // Remove this, as Customer.io SDK handles this automatically DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() } } } return true } // No manual push methods needed - CioAppDelegateWrapper handles everything } ``` ### SwiftUI[](#swiftui-apn) If you’re using SwiftUI, you’ll need to use the `@UIApplicationDelegateAdaptor` instead of the `@main` attribute. See the *Before* sample to see what needs to change and the *After* sample to see the new pattern. Before (4.x) #### Before (4.x)[](#Before \(4.x\)) ```swift import SwiftUI import CioMessagingPushAPN import UserNotifications @main struct MyApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { ContentView() } } } class AppDelegate: NSObject, UIApplicationDelegate { // Similar manual push handling as UIKit example above } ``` After (4.3) #### After (4.3)[](#After \(4.3\)) ```swift import SwiftUI import CioMessagingPushAPN import UserNotifications @main struct MyApp: App { @UIApplicationDelegateAdaptor(CioAppDelegateWrapper.self) var appDelegate var body: some Scene { WindowGroup { ContentView() } } } class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Initialize push with wrapper MessagingPushAPN.initialize(withConfig: MessagingPushConfigBuilder().build()) return true } // No manual push methods needed } ``` ## Update with FCM (Firebase Cloud Messaging)[](#update-appdelegate-fcm) ### UIKit[](#uikit-fcm) Update your `AppDelegate.swift` file to use the new `CioAppDelegateWrapper` pattern. See the *Before* sample to see what needs to change and the *After* sample to see the new pattern. Before (4.x) #### Before (4.x)[](#Before \(4.x\)) ```swift import UIKit import CioMessagingPushFCM import UserNotifications import Firebase import FirebaseMessaging @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Configure Firebase FirebaseApp.configure() // Set FCM messaging delegate Messaging.messaging().delegate = self // Register for push notifications UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in if granted { DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() } } } return true } // Manual push handling methods func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { Messaging.messaging().apnsToken = deviceToken MessagingPush.shared.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { MessagingPush.shared.application(application, didFailToRegisterForRemoteNotificationsWithError: error) } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { MessagingPush.shared.application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler) } } extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { // Handle FCM token } } ``` After (4.3) #### After (4.3)[](#After \(4.3\)) ```swift import UIKit import CioMessagingPushFCM import UserNotifications import Firebase import FirebaseMessaging @main class AppDelegateWithCioIntegration: CioAppDelegateWrapper {} class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Configure Firebase FirebaseApp.configure() // Set FCM messaging delegate Messaging.messaging().delegate = self // Initialize push FCM with wrapper - handles all push methods automatically MessagingPushFCM.initialize(withConfig: MessagingPushConfigBuilder().build()) // Register for push notifications // You can move this line to any part of your app. It's not critical to call it in this method. UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in if granted { // Remove this, as Customer.io SDK handles this automatically DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() } } } return true } // No manual push methods needed - wrapper handles everything } extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { // Handle FCM token - Customer.io SDK will also receive this automatically } } ``` ### SwiftUI[](#swiftui-fcm) If you’re using SwiftUI, you’ll need to use the `@UIApplicationDelegateAdaptor` instead of the `@main` attribute. See the *Before* sample to see what needs to change and the *After* sample to see the new pattern. Before (4.x) #### Before (4.x)[](#Before \(4.x\)) ```swift import SwiftUI import CioMessagingPushFCM import UserNotifications @main struct MyApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { ContentView() } } } class AppDelegate: NSObject, UIApplicationDelegate { // Similar manual push handling as UIKit example above } ``` After (4.3) #### After (4.3)[](#After \(4.3\)) ```swift import SwiftUI import CioMessagingPushFCM import UserNotifications @main struct MyApp: App { @UIApplicationDelegateAdaptor(CioAppDelegateWrapper.self) var appDelegate var body: some Scene { WindowGroup { ContentView() } } } class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Initialize push with wrapper MessagingPushAPN.initialize(withConfig: MessagingPushConfigBuilder().build()) return true } // No manual push methods needed } ``` ## Important Notes[](#important-notes) 1. **Manual push handling methods are not required**: the `CioAppDelegateWrapper` automatically records information from following methods. But you can still use these methods if you want to add custom push handling: * `didRegisterForRemoteNotificationsWithDeviceToken` * `didFailToRegisterForRemoteNotificationsWithError` * `didReceiveRemoteNotification` * All other push-related delegate methods 2. **The `@main` attribute** - Must be on the wrapper class, not your AppDelegate. ## Troubleshooting[](#troubleshooting) If push notifications stop working after you update your implementation: 1. Make sure that you’ve added the `@main` attribute to the wrapper class 2. Verify that you’ve removed `@main` from your original AppDelegate 3. Check that you’re calling `MessagingPushAPN.initialize()` or `MessagingPushFCM.initialize()` 4. If you encounter some unexpected behavior and want to test is it related to new Push Notification tracking system, just comment the following line and compare `class AppDelegateWithCioIntegration: CioAppDelegateWrapper {}` with the original AppDelegate. --- ## Whats new > 4.x upgrade **Source:** /integrations/sdk/react-native/whats-new/4.x-upgrade # Upgrade from 3.4x to 4x This page provides steps to help you upgrade from react native 3.4 or later so you understand the development effort required to update your app and take advantage of the latest features. ## What changed?[](#what-changed) This update provides native support for our new integrations framework. While this represents a significant change “under the hood,” we’ve tried to make it as seamless as possible for you; much of your implementation remains the same. This move also adds two additional features: * **Support for anonymous tracking**: you can send events and other activity for anonymous users, and we’ll reconcile that activity with a person when you identify them. * **Built-in lifecycle events**: the SDK now automatically captures events like “Application Installed” and “Application Updated” for you. * **New device-level data**: the SDK captures the device `name` and other device-level context for you. ## Upgrade process[](#upgrade-process) You’ll update initialization calls for the SDK itself and the push and/or in-app messaging modules. **As a part of this process, your credentials change**. You’ll need to set up a new data inAn integration that feeds data *into* Customer.io. integration in Customer.io and get a new *CDP API Key*. But you’ll *also* need to keep your previous `siteId` as a `migrationSiteId` when you initialize the SDK. The `migrationSiteId` is a key helps the SDK send remaining traffic when people update your app. When you’re done, you’ll also need to change a few base properties to fit the new APIs. In general, `identifier` becomes `userId`, `body` becomes `traits`, and `data` becomes `properties`. ### 1\. Get your new *CDP API Key*[](#1-get-your-new-cdp-api-key) The new version of the SDK requires you to set up a new data inAn integration that feeds data *into* Customer.io. integration in Customer.io. As a part of this process, you’ll get your *CDP API Key*. 1. Go to [*Data & Integrations* > *Integrations*](https://fly.customer.io/workspaces/last/journeys/integrations/all/overview) and click **Add Integration**. 2. Select **React Native**. 3. Enter a *Name* for your integration, like “My React Native App”. 4. We’ll present you with a code sample containing a `cdpApiKey` that you’ll use to initialize the SDK. Copy this key and keep it handy. 5. Click **Complete Setup** to finish setting up your integration. [![Set your name, get your CDP API Key, and click Complete Setup](https://customer.io/images/cdp-react-native-source-setup.png)](#40e573b250197f1c640f816429d6bf9b-lightbox) Remember, you can also [connect your React Native app to services outside of Customer.io](/integrations/data-out/add-destination/)—like your analytics provider, data warehouse, or CRM. ### 2\. Update your initialization[](#2-update-your-initialization) You’ll initialize the new version of the SDK and its packages with `CioConfig` objects instead of `CustomerioConfig`. While we’ve listed all the new configuration options, you’ll want to pay close attention to the following changes: * `CustomerIOEnv` is no longer necessary. * `Region` becomes `CioRegion`. * `siteId` becomes `migrationSiteId`. * You’ll initialize the SDK with `initialize(config)` instead of `initialize(env, config)`. If you previously used the `backgroundQueueMinNumberOfTasks` or `backgroundQueueSecondsDelay` options, you should remove them from your configuration as well. These options are no longer supported, and may cause build errors if you use strict type checking. ```javascript import { CioLogLevel, CioRegion, CustomerIO, CioConfig } from 'customerio-reactnative'; const config: CioConfig = { cdpApiKey: 'cdp_api_key', // Mandatory migrationSiteId: 'site_id', // For migration region: CioRegion.US, logLevel: CioLogLevel.Debug, trackApplicationLifecycleEvents: true, inApp: { siteId: 'site_id', // this removes the use of enableInApp and simplifies in-app configuration }, push: { android: { pushClickBehavior: PushClickBehaviorAndroid.ActivityPreventRestart } } }; CustomerIO.initialize(config) ``` ### 3\. Update your AppDelegate push notification handler[](#3-update-your-appdelegate-push-notification-handler) In your `MyAppPushNotificationsHandler.swift` (or the associated file where you add a push notification handler in your main target), you can remove the `CioTracking` module and the `initialize` method. **If you write native code in Objective-C**, you’ll also need to update your `MessagingPushAPN` or `MessagingPushFCM` initialization. We’ve highlighted the lines you’ll need to remove or modify in the code sample below. APN #### APN[](#APN) ```swift import Foundation import CioMessagingPushAPN // remove this line import CioTracking @objc public class MyAppPushNotificationsHandler : NSObject { public override init() {} @objc(setupCustomerIOClickHandling) public func setupCustomerIOClickHandling() { // remove this line CustomerIO.initialize(siteId: "siteId", apiKey: "apiKey", region: .US) { config in } // update this line to MessagingPushAPN.initialize(withConfig: MessagingPushConfigBuilder().build()) } @objc(application:deviceToken:) public func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { MessagingPush.shared.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) } @objc(application:error:) public func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { MessagingPush.shared.application(application, didFailToRegisterForRemoteNotificationsWithError: error) } } ``` FCM #### FCM[](#FCM) ```swift import Foundation import CioMessagingPushFCM import FirebaseMessaging // remove this line import CioTracking @objc public class MyAppPushNotificationsHandler : NSObject { public override init() {} @objc(setupCustomerIOClickHandling) public func setupCustomerIOClickHandling() { // remove this line CustomerIO.initialize(siteId: Env.siteId, apiKey: Env.apiKey, region: Region.US) { config in } // update this line to MessagingPushFCM.initialize(withConfig: MessagingPushConfigBuilder().build()) } // Register device on receiving a device token (FCM) @objc(didReceiveRegistrationToken:fcmToken:) public func didReceiveRegistrationToken(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { MessagingPush.shared.messaging(messaging, didReceiveRegistrationToken: fcmToken) } } ``` ### 4\. Update your NotificationService push notification handler[](#4-update-your-notificationservice-push-notification-handler) In your `NotificationServicePushHandler.swift` (or the associated file where you add a push notification handler in NotificationServiceExtension), you can remove the `CioTracking` module and the `initialize` method. **If you write native code in Objective-C**, you’ll also need to update your `MessagingPushAPN` or `MessagingPushFCM` initialization. We’ve highlighted the lines you’ll need to remove or modify in the code sample below. APN #### APN[](#APN) ```swift import Foundation import UserNotifications import CioMessagingPushAPN // remove this line import CioTracking @objc public class NotificationServicePushHandler: NSObject { public override init() {} @objc(didReceive:withContentHandler:) public func didReceive( _ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void ) { // remove this line CustomerIO.initialize(siteId: "siteId", apiKey: "apiKey", region: .US) { config in } // update this line to MessagingPushAPN.initializeForExtension( withConfig: MessagingPushConfigBuilder(cdpApiKey: "cdpApiKey") // Optional: specify region where your Customer.io account is located (.US or .EU). Default: US // .region(.US) .build() ) MessagingPush.shared.didReceive(request, withContentHandler: contentHandler) } @objc(serviceExtensionTimeWillExpire) public func serviceExtensionTimeWillExpire() { MessagingPush.shared.serviceExtensionTimeWillExpire() } } ``` FCM #### FCM[](#FCM) ```swift import Foundation import UserNotifications import CioMessagingPushFCM // remove this line import CioTracking @objc public class NotificationServicePushHandler: NSObject { public override init() {} @objc(didReceive:withContentHandler:) public func didReceive( _ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void ) { // remove this line CustomerIO.initialize(siteId: "siteId", apiKey: "apiKey", region: .US) { config in } // update this line to MessagingPushFCM.initializeForExtension( withConfig: MessagingPushConfigBuilder(cdpApiKey: "cdpApiKey") // Optional: specify region where your Customer.io account is located (.US or .EU). Default: US // .region(.US) .build() ) MessagingPush.shared.didReceive(request, withContentHandler: contentHandler) } @objc(serviceExtensionTimeWillExpire) public func serviceExtensionTimeWillExpire() { MessagingPush.shared.serviceExtensionTimeWillExpire() } } ``` ### 5\. Update your `identify` call[](#5-update-your-identify-call) Our APIs changed slightly in this release. We’ve done our best to make the new APIs as similar as possible to the old ones. The names of a few properties that you’ll pass in your calls have changed, but their functionality has not. * `identify`: `identifier` becomes `userId` and `body` becomes `traits` * `track` and `screen` calls are structured the same as previous versions, but the `data` object is now called `properties`. We’ve highlighted changes in the sample below. ```javascript //identify: identifier becomes userId, body becomes traits CustomerIO.identify({ userId: "user_id", traits: { first_name: "user_name", email: "email_identifier", }, }); //track: no significant change to method //in Customer.io data object renamed properties CustomerIO.track("track_event_name", { propertyName: propertyValue }); //screen: no significant change to method. //name becomes title, data object renamed properties CustomerIO.screen("screen_event_name", { propertyName: propertyValue }); ``` ## Configuration Changes[](#configuration-changes) As a part of this release, we’ve changed a few configuration options when you initialize the SDK. You’ll use `CioConfig` to set your configuration options. The following table shows the changes to the configuration options. Field Type Default Description `cdpApiKey` string Replaces `apiKey`; required to initialize the SDK and send data into Customer.io. `migrationSiteId` string Replaces `siteId`; required if you’re updating from 2.x. This is the key representing your previous version of the SDK. `trackApplicationLifeCycleEvents` boolean `true` When true, the SDK automatically tracks application lifecycle events (like *Application Installed*). `inApp` object Replaces the former `enableInApp` option, providing a place to set in-app configuration options. For now, it takes a single property called `siteId`. `push` object Replaces the former `enablePush` option, providing a place to set push configuration options. For now, it only takes the `android.pushClickBehavior` setting. `backgroundQueueMinNumberOfTasks` removed This option is no longer available. `backgroundQueueSecondsDelay` removed This option is no longer available. --- ## Whats new > 5.x upgrade **Source:** /integrations/sdk/react-native/whats-new/5.x-upgrade # 4.x -> 5.0.0 Version 5.x of the Customer.io React Native SDK introduces Firebase wrapper support for FCM users that improves Firebase compatibility and simplifies push notification setup. ## What changed?[](#what-changed) Version 5.x introduces a Firebase wrapper that improves compatibility with Firebase Cloud Messaging (FCM) and other Firebase services in your app. ## Do you need to update to this version?[](#do-you-need-to-update-to-this-version) **You need to update to this version if** you use **FCM (Firebase Cloud Messaging)** for push notifications ## Update process[](#update-process) Add the `CioFirebaseWrapper` import to your Swift files that use `CioMessagingPushFCM`. Add the Firebase wrapper import to your `AppDelegate.swift` file: ```swift import UIKit import CioMessagingPushFCM import CioFirebaseWrapper // Add this import for FCM users import UserNotifications import Firebase import FirebaseMessaging @main class AppDelegateWithCioIntegration: CioAppDelegateWrapper {} ``` ## Troubleshooting[](#troubleshooting) If you see build errors related to Firebase after upgrading: 1. **Clean your build**: Run `cd ios && pod install && cd ..` then rebuild 2. **Check imports**: Ensure you’ve added `import CioFirebaseWrapper` to all files that import `CioMessagingPushFCM` --- ## Whats new > 6.x upgrade **Source:** /integrations/sdk/react-native/whats-new/6.x-upgrade # 5.x -> 6.0.0 Version 6.0.0 of the Customer.io React Native SDK requires the React Native new architecture. ## What changed?[](#what-changed) Version 6.0.0 removes support for React Native’s legacy architecture. This aligns with [React Native’s move to exclusively support their new architecture](https://reactnative.dev/blog/2025/12/10/react-native-0.83). You must migrate your app to use React Native’s new architecture to use this and future versions of the SDK. ## Do you need to update to this version?[](#do-you-need-to-update-to-this-version) We recommend updating to the latest SDK version. However, if your app uses the old React Native architecture and you’re not ready to migrate, you can continue using version 5.x until you’re ready to adopt the new architecture. ### Prerequisites[](#prerequisites) Before updating to SDK version 6.0.0, your app must use React Native’s new architecture. See [React Native’s documentation](https://reactnative.dev/) for more information. ## Update process[](#update-process) If your app already uses React Native’s new architecture, updating to Customer.io SDK version 6.0.0 is straightforward. There are no public API changes. ### 1\. Migrate to the new React Native architecture[](#1-migrate-to-the-new-react-native-architecture) If you haven’t migrated to React Native’s new architecture yet, see the [React Native documentation](https://reactnative.dev/blog/2024/10/23/the-new-architecture-is-here) for instructions. When your app is successfully running on the new architecture, you can update to Customer.io SDK version 6.0.0. ### 2\. Update the SDK version[](#2-update-the-sdk-version) Update your `package.json` to use version 6.0.0: ```bash npm install [email protected] # or yarn add [email protected] ``` ### 3\. Install dependencies[](#3-install-dependencies) For a clean installation to ensure codegen works properly: ```bash # Clean install rm -rf node_modules npm install # iOS cd ios && rm -rf Pods Podfile.lock && pod install && cd .. # Rebuild your app npm run ios # or npm run android ``` ### 4\. Test your integration[](#4-test-your-integration) Since there are no public API changes, your existing Customer.io SDK calls will continue to work. However, you should test your app after updating to ensure everything works as expected. ## Troubleshooting[](#troubleshooting) If you encounter build errors after updating, perform a clean build: ```bash # Clean install rm -rf node_modules npm install # iOS cd ios && rm -rf Pods Podfile.lock && pod install && cd .. # Android cd android && ./gradlew clean && cd .. # Rebuild npm run ios npm run android ``` If issues persist, ensure your app is properly configured for the React Native new architecture and that all dependencies support it.  Try our MCP server! Our MCP server includes an `integration` tool that can help you install and troubleshoot issues with our SDK, including problems with push and in-app notifications. See our [MCP server documentation](/ai/mcp-server/) for more information. --- ## Whats new > Changelog **Source:** /integrations/sdk/react-native/whats-new/changelog # Changelog Check out release history our React Native SDK. Stable releases have been tested thoroughly and are ready for use in your production apps. #### Need to upgrade? Select your current version to see all the features and fixes from your version to the latest release. 6.1.06.0.05.3.05.2.05.1.15.1.05.0.15.0.04.11.04.10.04.9.04.8.34.8.24.8.14.8.04.7.04.6.04.5.24.5.14.5.04.4.34.4.24.4.14.4.04.3.14.3.04.2.74.2.64.2.54.2.44.2.34.2.24.2.14.2.04.1.14.1.04.0.24.0.14.0.03.9.13.9.03.8.03.7.23.7.13.7.03.6.03.5.43.5.33.5.23.5.13.5.03.4.03.3.23.3.13.3.03.2.13.2.03.1.133.1.123.1.113.1.103.1.93.1.83.1.73.1.63.1.53.1.43.1.33.1.23.1.13.1.03.0.02.5.12.5.02.4.22.4.12.4.02.3.32.3.22.3.12.3.02.2.12.2.02.1.02.0.12.0.01.0.0 ### Breaking Changes ### Features ### Bug Fixes # 6.x Releases[](#6x-releases) * * * * ### 6.1.0[](#610) January 14, 2026[code changes](https://github.com/customerio/customerio-reactnative/compare/6.0.0...6.1.0) ### Features * In-app messages now support SSE (Server-Sent Events) as an alternative to polling, reducing latency and improving message delivery efficiency ([#555](https://github.com/customerio/customerio-reactnative/issues/555)) ([a777c69](https://github.com/customerio/customerio-reactnative/commit/a777c6958cc8b48116687a0a521a44fbee4582bd)) * ### 6.0.0[](#600) December 24, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/5.3.0...6.0.0) ### âš  BREAKING CHANGES * Apps must now use React Native new architecture to use this and future versions of the SDK. No public API changes were made, so existing apps already using the new architecture should not be affected. (#553) ### Features * Support for old React Native architecture has been removed to align with official React Native recommendations ([#553](https://github.com/customerio/customerio-reactnative/issues/553)) ([4956640](https://github.com/customerio/customerio-reactnative/commit/495664072c744d4a2005bce243b2a6b5564d2c0f)) * Added support for lead capture with anonymous messages # 5.x Releases[](#5x-releases) * * * * ### 5.3.0[](#530) December 4, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/5.2.0...5.3.0) ### Features * Update example app icons ([#549](https://github.com/customerio/customerio-reactnative/issues/549)) ([5e6f255](https://github.com/customerio/customerio-reactnative/commit/5e6f2552f96c7a385952482354bcbb7921426858)) * ### 5.2.0[](#520) November 20, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/5.1.1...5.2.0) ### Features * Exposed `trackMetric` method for manually tracking push notification metrics. This is useful when using multiple push providers or displaying notifications without relying on Customer.io SDK. ([#539](https://github.com/customerio/customerio-reactnative/issues/539)) ([43deefe](https://github.com/customerio/customerio-reactnative/commit/43deefef5afe66161ed954948c5f0388ba79be37)) * ### 5.1.1[](#511) November 14, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/5.1.0...5.1.1) ### Bug Fixes * expose in-app listener for expo ([#538](https://github.com/customerio/customerio-reactnative/issues/538)) ([48687f0](https://github.com/customerio/customerio-reactnative/commit/48687f07556645f58c569fd11bea7720494da829)) * ### 5.1.0[](#510) October 30, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/5.0.1...5.1.0) ### Features * Updated the SDK and dependencies for Android 16 compatibility, including minor updates to better support newer OS restrictions and behavior changes. ([#533](https://github.com/customerio/customerio-reactnative/issues/533)) ([fd567f3](https://github.com/customerio/customerio-reactnative/commit/fd567f3db1841e0f59a19de00e4449b2b74b9a8d)) ### ⚠️ Notes * Apps now may need to update their `compileSdk` version to `36` and Gradle version to at least `8.9.3` to ensure compatibility with updated dependencies and to successfully build against Android 16. * ### 5.0.1[](#501) October 24, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/5.0.0...5.0.1) ### Bug Fixes * prevent message type crash in release builds ([#531](https://github.com/customerio/customerio-reactnative/issues/531)) ([587012b](https://github.com/customerio/customerio-reactnative/commit/587012b2d2a7035eb035852ba5a2fd4a033f1306)) * ### 5.0.0[](#500) October 17, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.11.0...5.0.0) ### âš  BREAKING CHANGES * Add CioFirebaseWrapper to pull in Firebase specific services (#528) ### Features * Add CioFirebaseWrapper to pull in Firebase specific services ([#528](https://github.com/customerio/customerio-reactnative/issues/528)) ([3c11e2e](https://github.com/customerio/customerio-reactnative/commit/3c11e2e3d800adfc1bf1a1a6d7a2700975370ea4)) # 4.x Releases[](#4x-releases) * * * * ### 4.11.0[](#4110) October 8, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.10.0...4.11.0) ### Features * You can now send banners, modals, pop-ups, and surveys to anonymous visitors —no ID or email required. ([#526](https://github.com/customerio/customerio-reactnative/issues/526)) ([f114d29](https://github.com/customerio/customerio-reactnative/commit/f114d290c6a4c2539b622a483a9c0bc440650a89)) * ### 4.10.0[](#4100) October 7, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.9.0...4.10.0) ### Features * Improve push notificaiton delivery receipts delay ([#524](https://github.com/customerio/customerio-reactnative/issues/524)) ([0c04cd2](https://github.com/customerio/customerio-reactnative/commit/0c04cd28abbb185de832a78ea92e79bee94efb00)) * ### 4.9.0[](#490) October 4, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.8.3...4.9.0) ### Breaking Features * Support for Kotlin 2+ is added (#591) (b5f94ff) but this also means apps needs to have the following minimum requirement * Gradle: 8.0 or later * Android Gradle Plugin (AGP): 8.0 or later (8.2+ recommended) * Kotlin: 1.9.20 or later (2.0+ required if using Kotlin Multiplatform or K2-specific features) ### Features * Upgrade Kotlin and AGP versions ([#284](https://github.com/customerio/customerio-flutter/issues/284)) ([ed9da81](https://github.com/customerio/customerio-flutter/commit/ed9da81ad05500e07224391e696f725cc75d4b76)) * Added support for queue sticky sessions from latest Android native SDK (customerio/customerio-android#598) * Align public API with other CIO SDK platforms from latest Android native SDK (customerio/customerio-android#600) ### Bug Fixes * Resolved a crash when dismissing in app messages using back press during initial loading phase. Users can now safely navigate away from messages without encountering unexpected app crashes. from latest Android native SDK (customerio/customerio-android#608) * Fix in-app inline tabbed bug (#521) * ### 4.8.3[](#483) September 19, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.8.2...4.8.3) ### Bug Fixes * Disabled native logging on `armeabi` devices using new architecture to prevent rare crashes from low level C++ code, no functional impact to end users. ([#516](https://github.com/customerio/customerio-reactnative/issues/516)) ([c406795](https://github.com/customerio/customerio-reactnative/commit/c40679583d85abd6ca0046726f1257fd4dc6d52a)) * ### 4.8.2[](#482) September 9, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.8.1...4.8.2) ### Improvements * Ensures in-app message event listener is set during module initialization to better support Expo auto initialization and avoid direct React imports that could lead to runtime issues. Behavior remains unchanged for manually initialized setups. ([#513](https://github.com/customerio/customerio-reactnative/issues/513)) ([ad4b169](https://github.com/customerio/customerio-reactnative/commit/ad4b169058699f1143adce029e8647301693502b)) * ### 4.8.1[](#481) September 5, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.8.0...4.8.1) ### Bug Fixes * Add missing InApp dismissMessage() for legacy RN architecture ([#511](https://github.com/customerio/customerio-reactnative/issues/511)) ([6ab19f5](https://github.com/customerio/customerio-reactnative/commit/6ab19f55048b6ff69fdaaa3f643120f312d09308)) * ### 4.8.0[](#480) August 27, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.7.0...4.8.0) ### Improvements * Updated Native iOS SDK from `3.11.0` to `3.13.1` which includes following updates: ([#507](https://github.com/customerio/customerio-reactnative/issues/507)) ([e5821d3](https://github.com/customerio/customerio-reactnative/commit/e5821d33abc21a0289f3b3795e1f271c6e51cb62)) * Added support for queue sticky sessions * Align public API with other CIO SDK platforms * Fixed build issues on Xcode 26 beta that only affected apps using CocoaPods * Fixed an issue where custom scheme URLs were not opening when using FCM with `CioAppDelegateWrapper` * ### 4.7.0[](#470) August 22, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.6.0...4.7.0) ### Features * Improved logger listener setup to improve performance and reliability on low-end devices ([#493](https://github.com/customerio/customerio-reactnative/pull/493)) ([e6d37e4](https://github.com/customerio/customerio-reactnative/commit/e6d37e4988e7b604d15cbae064a53aceeb2bbb38)) * Added support for optional automatic SDK initialization with Expo plugin, reducing setup complexity ([#504](https://github.com/customerio/customerio-reactnative/issues/504)) ([d3ed7f9](https://github.com/customerio/customerio-reactnative/commit/d3ed7f92f002113919d669a66f4fd4fedc11a56f)) * ### 4.6.0[](#460) July 29, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.5.2...4.6.0) ### ⚙️ Utilizing React Native New Architecture * This release fully adopts React Native’s new architecture using Fabric and TurboModules while maintaining compatibility with old architecture. No changes to public API, apps will automatically use appropriate setup based on their configuration. ([#484](https://github.com/customerio/customerio-reactnative/issues/484)) ([2476ee9](https://github.com/customerio/customerio-reactnative/commit/2476ee940d32e6b200c4cbea943413debbe51e75)) * ### 4.5.2[](#452) July 24, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.5.1...4.5.2) ### Fixes and Tooling update * Support for FCM 12.x is added which requires a minimum iOS deployment target of 15. If you’re using FCM module, ensure your deployment target and tooling are up to date. Or lock Firebase to 11.x to avoid compatibility issues * Fixes a crash when build attributes from device are nullable (iOS 3.11.0, Android 4.7.1) ([#471](https://github.com/customerio/customerio-reactnative/issues/471)) ([f8b78eb](https://github.com/customerio/customerio-reactnative/commit/f8b78eb63a73d0e7849be1d8322dfd6b8b6f8d1b)) * ### 4.5.1[](#451) July 21, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.5.0...4.5.1) ### Bug Fixes * Fixed build issue on iOS by adding a default implementation for an internal SPI method in `DeepLinkUtil`, preventing conformance errors with `BUILD_LIBRARY_FOR_DISTRIBUTION = YES` ([#466](https://github.com/customerio/customerio-reactnative/issues/466)) ([ccc149c](https://github.com/customerio/customerio-reactnative/commit/ccc149c17dd6e57881d2d3a864f63cb69958d91a)) * ### 4.5.0[](#450) July 17, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.4.3...4.5.0) ### Features * Add ability to configure messaging channels for local notifications ([#462](https://github.com/customerio/customerio-reactnative/issues/462)) ([2ddf8f5](https://github.com/customerio/customerio-reactnative/commit/2ddf8f500515e86baf7cb16b9ce56633a47b7c6b)) * ### 4.4.3[](#443) July 9, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.4.2...4.4.3) ### Bug Fixes * Preserve numeric types when doing sanitization for JSON ([#460](https://github.com/customerio/customerio-reactnative/issues/460)) ([b1e5080](https://github.com/customerio/customerio-reactnative/commit/b1e5080dd1f225d37bafd597b0bab3185d14ead8)) * ### 4.4.2[](#442) July 4, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.4.1...4.4.2) ### Bug Fixes * Update RN to v0.80 ([#447](https://github.com/customerio/customerio-reactnative/issues/447)) ([bf8ddf3](https://github.com/customerio/customerio-reactnative/commit/bf8ddf311ca3675f7298bdcfb76d9fe38558a3d5)) * ### 4.4.1[](#441) June 30, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.4.0...4.4.1) ### Bug Fixes * Fixed a bug where apps using older versions of React Native with new architecture could fail to locate native inline wrapper component, causing integration issues on iOS ([#454](https://github.com/customerio/customerio-reactnative/issues/454)) ([557aa0e](https://github.com/customerio/customerio-reactnative/commit/557aa0e9238174e07e497a1a7ec7b77d2a03e9de)) * ### 4.4.0[](#440) June 26, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.3.1...4.4.0) ### Features * Added support for inline in-app messages. Inline in-app messages act like a part of the content on your page. They let you dynamically populate parts of your app and talk to your customers without interrupting their experience. ([#453](https://github.com/customerio/customerio-reactnative/issues/453)) ([d11041b](https://github.com/customerio/customerio-reactnative/commit/d11041b08f173ec00a718d5f3892ff1000bebb30)) * ### 4.3.1[](#431) June 2, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.3.0...4.3.1) ### Bug Fixes * Fixed an issue where the SDK enforced a strict version of firebase messaging, preventing integration with newer versions. * Fixes the bug where multi screen in-app messages might dismiss earlier than intended ([#437](https://github.com/customerio/customerio-reactnative/issues/437)) ([e513bfb](https://github.com/customerio/customerio-reactnative/commit/e513bfb62696bff879c1773f14e73e552f0d0161)) * ### 4.3.0[](#430) May 30, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.2.7...4.3.0) ### Features * Includes new system for accessing device-token and tracking notifications events. This replaces old Swizzling-based system, in iOS SDK. The change increases stability and improves compatibility with other SDKs (like Firebase) ([#434](https://github.com/customerio/customerio-reactnative/issues/434)) ([f864e89](https://github.com/customerio/customerio-reactnative/commit/f864e892d2d150161567cb5e62c6b912618dc680)) * ### 4.2.7[](#427) May 9, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.2.6...4.2.7) ### Bug Fixes * Fixes the issues where wrong source of commonjs was picked([#426](https://github.com/customerio/customerio-reactnative/issues/426)) ([0468ddd](https://github.com/customerio/customerio-reactnative/commit/0468ddd96d7d376dc8980c8f1172635f8759bded)) * Fixes the issue where pod install command would fail because they pods different linkage requirement. ([#423](https://github.com/customerio/customerio-reactnative/issues/423)) ([bbd434c](https://github.com/customerio/customerio-reactnative/commit/bbd434c610f0eea17455d9cc806a4faf77c8951d)) * ### 4.2.6[](#426) April 29, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.2.5...4.2.6) ### Bug Fixes * Fixes the compatibility issues with React Native v0.79 ([7c74c4d](https://github.com/customerio/customerio-reactnative/commit/7c74c4d76c63e671eccc1bc5e24df89243a974f9)) * ### 4.2.5[](#425) April 28, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.2.4...4.2.5) ### Bug Fixes * Updated author for package published on NPM ([88e4da6](https://github.com/customerio/customerio-reactnative/commit/88e4da65ed117dee221c28779ba02b6958083526)) * ### 4.2.4[](#424) April 14, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.2.3...4.2.4) ### Bug Fixes * Added support for null handling in nested structures in attributes/traits ([#402](https://github.com/customerio/customerio-reactnative/issues/402)) ([c9d1baf](https://github.com/customerio/customerio-reactnative/commit/c9d1bafd33edca87d70f6384aeeb92fef9752022)) * ### 4.2.3[](#423) April 3, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.2.2...4.2.3) Updates the iOS native dependency from `3.7.2` to `3.8.1` and Android native dependency from `4.5.3` to `4.5.5` which includes the following improvements. ### Bug Fixes * \[iOS\] Fixed an issue where the “Application Installed” event was incorrectly triggered on every app launch instead of only after the initial installation. * \[iOS\] Incorrectly scrolling content for in-app modal messages positioned top/bottom. [https://github.com/customerio/customerio-ios/pull/858](https://github.com/customerio/customerio-ios/pull/858) * \[Android\] Resolved syncing issues for events stored while in battery saver (offline) mode ([https://github.com/customerio/customerio-android/issues/498](https://github.com/customerio/customerio-android/issues/498)) ([6f3d16f](https://github.com/customerio/customerio-android/commit/6f3d16fe01a675cfa522099230baf03650cf9c42)) * \[Android\] Fixed the sequencing of screen tracking events for in-app messaging current screen state ([https://github.com/customerio/customerio-android/issues/500](https://github.com/customerio/customerio-android/issues/500)) ([6877daf](https://github.com/customerio/customerio-android/commit/6877daf98235ce9c96a2ce4932f188efb2c33a71)) * ### 4.2.2[](#422) February 19, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.2.1...4.2.2) ### Bug Fixes * Android 14 introduced strict rules for when apps are in the killed state, impacting push delivery tracking. This release fixes that.([#386](https://github.com/customerio/customerio-reactnative/issues/386)) ([e8a08a5](https://github.com/customerio/customerio-reactnative/commit/e8a08a511063bc236a7f411de0cabffeb1158907)) * ### 4.2.1[](#421) January 9, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.2.0...4.2.1) Updates the iOS native dependency from `3.7.1` to `3.7.2` and Android native dependency from `4.5.0` to `4.5.2` which includes the following improvements. ### Bug Fixes * \[Android\] Fixes the bug where device update/delete events would not migrate automatically when migrating from v3 to v4 ([https://github.com/customerio/customerio-android/issues/481](https://github.com/customerio/customerio-android/issues/481)) * \[Android & iOS\] Fixes in-app messages overlay background color being ignored from in-app message payload ([https://github.com/customerio/customerio-android/issues/485](https://github.com/customerio/customerio-android/issues/485)) ([https://github.com/customerio/customerio-ios/issues/843](https://github.com/customerio/customerio-ios/issues/843)) * ### 4.2.0[](#420) January 6, 2025[code changes](https://github.com/customerio/customerio-reactnative/compare/4.1.1...4.2.0) ### Features * Added ability to disable forwarding screen events to destinations/servers. Apps can still send screen events for local processing and use them for page rules in in-app messages by updating SDK configuration during initialization. ([#369](https://github.com/customerio/customerio-reactnative/issues/369)) ([055835e](https://github.com/customerio/customerio-reactnative/commit/055835ebdbb4147152e50d5ed6f1ae5ac18e6fdc)) * ### 4.1.1[](#411) November 20, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/4.1.0...4.1.1) ### Bug Fixes * Resolved compatibility issues with React Native 0.76 on iOS apps ([#359](https://github.com/customerio/customerio-reactnative/issues/359)) ([6fcdd91](https://github.com/customerio/customerio-reactnative/commit/6fcdd91403fea14344d2d293c9184447f516aadf)) * ### 4.1.0[](#410) November 14, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/4.0.2...4.1.0) Updates the iOS Native dependency from 3.5.1 to 3.6.0 which includes the following improvements. ### Features * This release introduces support for displaying larger in-app messages. ([#356](https://github.com/customerio/customerio-reactnative/issues/356)) ([72bda17](https://github.com/customerio/customerio-reactnative/commit/72bda17c434a41e72a4fa8a53d43fb2ff5dd0256)) ### Fixes * Fixes the push metric for the EU region by adding region support in the MessagingPush config in the notification extension ([https://github.com/customerio/customerio-ios/pull/836](https://github.com/customerio/customerio-ios/pull/836)) ### Improvement * Updated our SDK to use the v2 version of our in-app messages API. This will provide a more reliable experience for in-app messages. ([#834](https://github.com/customerio/customerio-ios/pull/834)) ([#461](https://github.com/customerio/customerio-android/issues/461)) * ### 4.0.2[](#402) October 25, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/4.0.1...4.0.2) Updates the iOS Native dependency from 3.5.0 to 3.5.1 which includes the following improvements. ### Bug Fixes * Dismisses the keyboard when an in-app message appears on the screen, ensuring uninterrupted user interaction ([#350](https://github.com/customerio/customerio-reactnative/issues/350)) ([74b2379](https://github.com/customerio/customerio-reactnative/commit/74b2379fb435e450b05321bf80ee18bff1d4a1ab)) * ### 4.0.1[](#401) October 16, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/4.0.0...4.0.1) ### Improvement * Updated the workflow to automatically update the Android native SDK version in the package, ensuring greater consistency and reducing manual intervention during updates. ([b477fbc](https://github.com/customerio/customerio-reactnative/commit/b477fbc8e4f742a521289e63d4aafe297a88cde0)) * ### 4.0.0[](#400) October 16, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.9.1...4.0.0) ### âš  BREAKING CHANGES * Data Pipelines Support: Sending your mobile data into our customer data platform (CDP) helps you support Journeys and the rest of your martech stack—analytics, data warehouses, CRMs, and more. (#349) > ***NOTE:*** Please follow the [migration guide](https://docs.customer.io/sdk/react-native/whats-new/4.x-upgrade/) for a seamless upgrade to this version. ### Features * Anonymous tracking: You can send anonymous events, and we’ll reconcile anonymous activity with your users when you identify them. ([#349](https://github.com/customerio/customerio-reactnative/issues/349)) ([6665c9f](https://github.com/customerio/customerio-reactnative/commit/6665c9f9c4a8d4f175bae86f0e0294e0dc3b8bd1)) * Built-in lifecycle events: the SDK now automatically captures events like “Application Installed” and “Application Updated” for you, so you better understand your users’ behaviors in your app. * New device data: The SDK captures complete device-level context, such as your audience’s screen dimensions, device names, and more. # 3.x Releases[](#3x-releases) * * * * ### 3.9.1[](#391) October 10, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.9.0...3.9.1) ### Features * Adds support for the latest versions of the Firebase dependency (>11) ([#344](https://github.com/customerio/customerio-reactnative/issues/344)) ([11d17e1](https://github.com/customerio/customerio-reactnative/commit/11d17e119f003593bc9a2dde7644bb2b3314bd45)) * ### 3.9.0[](#390) August 28, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.8.0...3.9.0) Updates the iOS Native dependency from 2.13.2 to 2.14.1 which includes the following improvements. ### Features * Reduced the time for first in-app message to be shown for newly identified profiles. For new profiles, in-app messages are now fetched as soon as the profile is identified.([#307](https://github.com/customerio/customerio-reactnative/issues/307)) ([cb272c2](https://github.com/customerio/customerio-reactnative/commit/cb272c2ce0b406a58c6ddad951682cc21071fbd5)) ### Bug Fixes * Explicitly switched threads to avoid forcing identify calls to be made on main thread. The SDK now automatically switches to appropriate thread, regardless of the thread used to make identify calls. * ### 3.8.0[](#380) July 2, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.7.2...3.8.0) ### Features * When using page rules and when an in-app messages need a second to load, the in-app message might appear after a user navigates to another page. We made changes to ensure the page-rules are checked after the message is loaded and immediately before it’s displayed in order to resolve this issue. ([#285](https://github.com/customerio/customerio-reactnative/issues/285)) ([478f644](https://github.com/customerio/customerio-reactnative/commit/478f6445fbc5dd2a92ae7ab32955c7fa5f92e812)) * ### 3.7.2[](#372) June 26, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.7.1...3.7.2) ### Bug Fixes * This release addresses a compatibility issue for apps that have installed two or more third-party SDKs (besides Customer.io SDK) that handle push notifications. While this issue was primarily reported by our Flutter customers, it could also affect native iOS and React Native applications. ([#283](https://github.com/customerio/customerio-reactnative/issues/283)) ([84d7259](https://github.com/customerio/customerio-reactnative/commit/84d7259045affbc84c00512711c39cee6f7b3d31)) * ### 3.7.1[](#371) June 13, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.7.0...3.7.1) ### Bug Fixes * References the [2.13.1](https://github.com/customerio/customerio-ios/releases/tag/2.13.1) version of the iOS SDK. This resolves a compatibility issue with 3rd party FCM Flutter and React Native SDKs. In some cases, the issue prevented push notifications from showing while the app was in the foreground when the 3rd party SDK and CIO SDK were both installed. ([#273](https://github.com/customerio/customerio-reactnative/issues/273)) ([456da1e](https://github.com/customerio/customerio-reactnative/commit/456da1ee961eccaa379cc8e8aee60e4937f6a30d)) * ### 3.7.0[](#370) April 18, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.6.0...3.7.0) ### Features * support for android gradle plugin 8 ([#258](https://github.com/customerio/customerio-reactnative/issues/258)) ([3544e66](https://github.com/customerio/customerio-reactnative/commit/3544e6626c90e0fb4f75e9bf7d94ea8e5da73906)) *Note:* * Android Gradle plugin version 7.4 or later is required. * JDK 17 is also required for Gradle 8. * ### 3.6.0[](#360) April 10, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.5.4...3.6.0) ### Features * privacy manifest files ([#254](https://github.com/customerio/customerio-reactnative/issues/254)) ([6a7c1f1](https://github.com/customerio/customerio-reactnative/commit/6a7c1f1c33188b15edc54fea60650fb58361a3ff)) * ### 3.5.4[](#354) April 9, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.5.3...3.5.4) ### Bug Fixes * added proguard rules for R8 strict mode ([#253](https://github.com/customerio/customerio-reactnative/issues/253)) ([6686206](https://github.com/customerio/customerio-reactnative/commit/6686206b4e08dda711ea304d2fc291f27152b0c6)) * ### 3.5.3[](#353) March 19, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.5.2...3.5.3) ### Bug Fixes * do not bundle .md files in cocoapods deployments ([a815336](https://github.com/customerio/customerio-reactnative/commit/a8153361ef08cff75d25e1eb3e4ed1da83fbc30a)) * ### 3.5.2[](#352) March 5, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.5.1...3.5.2) ### Bug Fixes * expo users reported app crash on didFailToRegisterForRemoteNotificationsWithError ([#244](https://github.com/customerio/customerio-reactnative/issues/244)) ([fb9a464](https://github.com/customerio/customerio-reactnative/commit/fb9a46438717b34c98c9f356daa9477bb2c7d95f)) * ### 3.5.1[](#351) February 26, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.5.0...3.5.1) ### Bug Fixes * edge case for image download in rich push ([#242](https://github.com/customerio/customerio-reactnative/issues/242)) ([04b63f8](https://github.com/customerio/customerio-reactnative/commit/04b63f86a303708585fcc365d465d02321fd1a3e)) * ### 3.5.0[](#350) February 22, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.4.0...3.5.0) ### Features * new header for polling interval and enable ios logging for in-app ([#237](https://github.com/customerio/customerio-reactnative/issues/237)) ([f77ff0e](https://github.com/customerio/customerio-reactnative/commit/f77ff0ecccab2d704d283e5e383065164ccd67e0)) * ### 3.4.0[](#340) February 12, 2024[code changes](https://github.com/customerio/customerio-reactnative/compare/3.3.2...3.4.0) ### Features * increase opened metrics reliability and 3rd party push SDK compatibility ([#236](https://github.com/customerio/customerio-reactnative/issues/236)) ([514b719](https://github.com/customerio/customerio-reactnative/commit/514b7197b7a252bc835eec2c000ae7669f4fb391)) * ### 3.3.2[](#332) November 14, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.3.1...3.3.2) ### Bug Fixes * improve android push click behavior ([#203](https://github.com/customerio/customerio-reactnative/issues/203)) ([8b1a836](https://github.com/customerio/customerio-reactnative/commit/8b1a836771811b408779d71362f11ab7a528a04e)) * ### 3.3.1[](#331) November 8, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.3.0...3.3.1) ### Bug Fixes * **ios:** memory exception during SDK initialization async tasks ([#217](https://github.com/customerio/customerio-reactnative/issues/217)) ([7e420cf](https://github.com/customerio/customerio-reactnative/commit/7e420cfad70e194fa0ae08c4778791480a7f92f3)) * ### 3.3.0[](#330) November 1, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.2.1...3.3.0) ### Features * fetch currently stored device token using JS method ([#216](https://github.com/customerio/customerio-reactnative/issues/216)) ([482f780](https://github.com/customerio/customerio-reactnative/commit/482f7809866fb32fc0834856f891345d3b7cb25f)) * ### 3.2.1[](#321) October 27, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.2.0...3.2.1) ### Bug Fixes * **ios:** when queue cannot find task, expect queue runs next task ([#213](https://github.com/customerio/customerio-reactnative/issues/213)) ([fc747a5](https://github.com/customerio/customerio-reactnative/commit/fc747a52072e43e1ef03fccd655671508f720167)) * ### 3.2.0[](#320) October 25, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.13...3.2.0) ### Features * in-app persistant messages ([#210](https://github.com/customerio/customerio-reactnative/issues/210)) ([ea2ed3f](https://github.com/customerio/customerio-reactnative/commit/ea2ed3fd896d8d17b6e9b99f239f8b8a65b5576c)) * ### 3.1.13[](#3113) October 18, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.12...3.1.13) ### Bug Fixes * in-app positioning issue ([#208](https://github.com/customerio/customerio-reactnative/issues/208)) ([465b107](https://github.com/customerio/customerio-reactnative/commit/465b107eb192578ae02a4396746ce16b8c09372f)) * ### 3.1.12[](#3112) October 11, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.11...3.1.12) ### Bug Fixes * remove BQ tasks register device with empty profile identifier ([#207](https://github.com/customerio/customerio-reactnative/issues/207)) ([4cb9cbc](https://github.com/customerio/customerio-reactnative/commit/4cb9cbc9ec061f7129eded83db3e4ea946cf490b)) * ### 3.1.11[](#3111) September 28, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.10...3.1.11) ### Bug Fixes * stack-overflow caused by BQ recursion ([#204](https://github.com/customerio/customerio-reactnative/issues/204)) ([49dba31](https://github.com/customerio/customerio-reactnative/commit/49dba318d9706f50005ccd16aeab3f99d6ad044b)) * ### 3.1.10[](#3110) September 7, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.9...3.1.10) ### Bug Fixes * concurrency issue in-app ([#197](https://github.com/customerio/customerio-reactnative/issues/197)) ([eb2d1fb](https://github.com/customerio/customerio-reactnative/commit/eb2d1fbff2fb17f9b3569f4b843817991de3de7c)) * ### 3.1.9[](#319) September 5, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.8...3.1.9) ### Bug Fixes * added url path encoding ([#194](https://github.com/customerio/customerio-reactnative/issues/194)) ([cd83f4b](https://github.com/customerio/customerio-reactnative/commit/cd83f4bb42160964d53c05c9afa58888791b0f92)) * ### 3.1.8[](#318) August 28, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.7...3.1.8) ### Bug Fixes * include required messagingpush dependency for no push configuration ([#187](https://github.com/customerio/customerio-reactnative/issues/187)) ([78bbc63](https://github.com/customerio/customerio-reactnative/commit/78bbc63035743714c9fc6a3ad7ad046312e20ca6)) * ### 3.1.7[](#317) July 26, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.6...3.1.7) ### Bug Fixes * support json array in attributes ([#180](https://github.com/customerio/customerio-reactnative/issues/180)) ([eb667a6](https://github.com/customerio/customerio-reactnative/commit/eb667a6d70a6a23afda440b42b357f07af00f07b)) * ### 3.1.6[](#316) July 24, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.5...3.1.6) ### Bug Fixes * in-app messages not displaying for release builds on Android ([#174](https://github.com/customerio/customerio-reactnative/issues/174)) ([973d1cc](https://github.com/customerio/customerio-reactnative/commit/973d1cc811c178c75e50a0016342b0e8bc066114)) * ### 3.1.5[](#315) July 21, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.4...3.1.5) ### Bug Fixes * sdk ignores requests when initializing the SDK from react native and from native iOS ([#173](https://github.com/customerio/customerio-reactnative/issues/173)) ([8bc7beb](https://github.com/customerio/customerio-reactnative/commit/8bc7beb338869bd69eb06f60f8f101a8106ee9dc)) * ### 3.1.4[](#314) July 20, 2023[code changes](https://customer.io/docs/sdk/react-native/push-notifications/push/%29.) **Note: 3.1.4 contains a known issue when the Customer.io native iOS SDK is initialized in the `AppDelegate` using `CustomerIO.initialize()` or `[pnHandlerObj initializeCioSdk];` as documented in our [React Native push setup documentation](https://customer.io/docs/sdk/react-native/push-notifications/push/). It’s recommended to use 3.1.3 until a newer version has been released.** ### Bug Fixes * deinit cleanup repo bad memory access ([#171](https://github.com/customerio/customerio-reactnative/issues/171)) ([25f10b1](https://github.com/customerio/customerio-reactnative/commit/25f10b1c07796cb8931b9865bb05ee2d10cffe3f)) * ### 3.1.3[](#313) July 14, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.2...3.1.3) ### Bug Fixes * hardcode android native SDK version ([#167](https://github.com/customerio/customerio-reactnative/issues/167)) ([be03bd5](https://github.com/customerio/customerio-reactnative/commit/be03bd5dfff7fdf142958c8ddf5f2f9c1ad21e2b)) * ### 3.1.2[](#312) July 12, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.1...3.1.2) ### Bug Fixes * gist migration and crash fix ([#165](https://github.com/customerio/customerio-reactnative/issues/165)) ([01a3074](https://github.com/customerio/customerio-reactnative/commit/01a3074b2e7f1897b55d4e28b825e849e7e1e693)) > Note: We’ve made updates to our [installation instructions for in-app for Android](https://www.customer.io/docs/sdk/android/getting-started/#install). Please refer to them as they reflect our new streamlined process which no longer necessitates a previously required dependency for in-app messages. * ### 3.1.1[](#311) July 10, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.1.0...3.1.1) ### Bug Fixes * iOS bad memory access crash ([#164](https://github.com/customerio/customerio-reactnative/issues/164)) ([55b72a7](https://github.com/customerio/customerio-reactnative/commit/55b72a7d128b97eed4577a73132e7c8d32423cd5)) * ### 3.1.0[](#310) July 5, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/3.0.0...3.1.0) ### Features * tracking push metrics from js ([#152](https://github.com/customerio/customerio-reactnative/issues/152)) ([6f51703](https://github.com/customerio/customerio-reactnative/commit/6f5170375498142f557870fdbb11104e91a74b83)) * ### 3.0.0[](#300) July 3, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.5.1...3.0.0) Installing and updating our React Native SDK got easier. Follow our [migration docs](https://customer.io/docs/sdk/react-native/updates-and-troubleshooting/migrate-upgrade/#upgrade-from-2x-to-3x) (it only requires modifications to your `Podfile`) to use version 3 our React Native SDK! ### âš  BREAKING CHANGES * auto-update native SDK and easier rich push install (#149) ### Bug Fixes * auto-update native SDK and easier rich push install ([#149](https://github.com/customerio/customerio-reactnative/issues/149)) ([7e56d1e](https://github.com/customerio/customerio-reactnative/commit/7e56d1e95752fe267b286e9e52b1d11cf0c2fd12)) # 2.x Releases[](#2x-releases) * * * * ### 2.5.1[](#251) July 3, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.5.0...2.5.1) ### Bug Fixes * android opened metrics when app in background ([#156](https://github.com/customerio/customerio-reactnative/issues/156)) ([fb14cce](https://github.com/customerio/customerio-reactnative/commit/fb14ccee755fefa004a4a55e7083d8d237f0a3b8)) * ### 2.5.0[](#250) June 28, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.4.2...2.5.0) ### Features * delete device token from profile ([#158](https://github.com/customerio/customerio-reactnative/issues/158)) ([0ff0eac](https://github.com/customerio/customerio-reactnative/commit/0ff0eac9e4ef44f9342cadc3b5669da3ec94e7e1)) * ### 2.4.2[](#242) June 6, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.4.1...2.4.2) ### Bug Fixes * update import from common to CioInternalCommon ([#147](https://github.com/customerio/customerio-reactnative/issues/147)) ([f0382a4](https://github.com/customerio/customerio-reactnative/commit/f0382a4823986be78680ce2dab468b2ea47d66fd)) * ### 2.4.1[](#241) June 5, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.4.0...2.4.1) ### Bug Fixes * installation breaks due to lefthook in postinstall ([#144](https://github.com/customerio/customerio-reactnative/issues/144)) ([e451443](https://github.com/customerio/customerio-reactnative/commit/e451443c655c7c7c2882778cc4bf05020107196a)) * ### 2.4.0[](#240) June 1, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.3.3...2.4.0) ### Features * dismiss in-app message ([#138](https://github.com/customerio/customerio-reactnative/issues/138)) ([55d4c62](https://github.com/customerio/customerio-reactnative/commit/55d4c6245b57a7090e0c0157996d2f43874b5297)) * ### 2.3.3[](#233) May 3, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.3.2...2.3.3) ### Bug Fixes * autoupdate to latest major version of iOS SDK ([#124](https://github.com/customerio/customerio-reactnative/issues/124)) ([7904c50](https://github.com/customerio/customerio-reactnative/commit/7904c5079df06776e603b9741bd8831170724041)) * ### 2.3.2[](#232) April 20, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.3.1...2.3.2) ### Bug Fixes * push opened metrics tracked on Android 12 ([#119](https://github.com/customerio/customerio-reactnative/issues/119)) ([dfd6fbd](https://github.com/customerio/customerio-reactnative/commit/dfd6fbdc1131b4f0f226480ef0ab8d67b16d4837)) * ### 2.3.1[](#231) April 20, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.3.0...2.3.1) ### Bug Fixes * typescript definition for optional data attributes ([#51](https://github.com/customerio/customerio-reactnative/issues/51)) ([4cec62a](https://github.com/customerio/customerio-reactnative/commit/4cec62abcf45f14f060d60fb2239c94d50f0a9a9)) * ### 2.3.0[](#230) April 4, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.2.1...2.3.0) ### Features * process push notifications received outside CIO SDK ([#117](https://github.com/customerio/customerio-reactnative/issues/117)) ([458472d](https://github.com/customerio/customerio-reactnative/commit/458472db12a0e7fa85ed920dc6810bd9697b0902)) * ### 2.2.1[](#221) March 28, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.2.0...2.2.1) ### Bug Fixes * native push permission status match typescript enum value ([#118](https://github.com/customerio/customerio-reactnative/issues/118)) ([38b7349](https://github.com/customerio/customerio-reactnative/commit/38b7349fbf06dfae16e374bdae7e0780dc153c01)) * ### 2.2.0[](#220) March 3, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.1.0...2.2.0) ### Features * push permission prompt ([#101](https://github.com/customerio/customerio-reactnative/issues/101)) ([1abe9b3](https://github.com/customerio/customerio-reactnative/commit/1abe9b33f05d125e4180a77207fad23080774550)) * ### 2.1.0[](#210) February 23, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.0.1...2.1.0) ### Features * deprecated organization id for in-app ([#71](https://github.com/customerio/customerio-reactnative/issues/71)) ([691f324](https://github.com/customerio/customerio-reactnative/commit/691f3240288672d59d915fa6998271591d4fef03)) * in-app event handler ([#89](https://github.com/customerio/customerio-reactnative/issues/89)) ([07e38f7](https://github.com/customerio/customerio-reactnative/commit/07e38f721acb7a613371c2fc3ac6872c3ea8cb38)) ### Bug Fixes * kotlin version in gradle props ([#96](https://github.com/customerio/customerio-reactnative/issues/96)) ([35dceb2](https://github.com/customerio/customerio-reactnative/commit/35dceb293b746845425e977df42e07959df8b60b)) * remove return from init ([#93](https://github.com/customerio/customerio-reactnative/issues/93)) ([d54174b](https://github.com/customerio/customerio-reactnative/commit/d54174bd48586b35033d18c90290e927f0fee970)) * update android sdk to range ([#94](https://github.com/customerio/customerio-reactnative/issues/94)) ([5a5a012](https://github.com/customerio/customerio-reactnative/commit/5a5a012d5c18eb25f54dd2211173ac948c75a989)) * ### 2.0.1[](#201) January 30, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/2.0.0...2.0.1) ### Bug Fixes * add register device token ([f865261](https://github.com/customerio/customerio-reactnative/commit/f86526193267692f68291406a61b75c5c700b2b6)) * ### 2.0.0[](#200) January 18, 2023[code changes](https://github.com/customerio/customerio-reactnative/compare/1.0.0...2.0.0) ### âš  BREAKING CHANGES * major iOS version bump to 2.0.0 - Detailed changes and migration guides are available [here](https://customer.io/docs/sdk/react-native/migrate-upgrade/#upgrade-from-1x-to-2x). ### Features * expo user agent ([#49](https://github.com/customerio/customerio-reactnative/issues/49)) ([377300c](https://github.com/customerio/customerio-reactnative/commit/377300c76d1e8856908c0b231bb8cc2c4b51f97c)) ### Bug Fixes * added support for android sdk 3.2.0 ([c0407be](https://github.com/customerio/customerio-reactnative/commit/c0407bed6df4582be8646ed44662d80a5868ad17)) * bumped ios version to 2.0.0 ([95f74b9](https://github.com/customerio/customerio-reactnative/commit/95f74b9c5e25e7d60e688de11459c83e30b50ae6)) * for test case failure due to file path ([ce43198](https://github.com/customerio/customerio-reactnative/commit/ce431983f45dcb0503e248a36393fff6fb12db15)) * support for expo client ([b641fd8](https://github.com/customerio/customerio-reactnative/commit/b641fd8b94ab023c8a3577dd47ebdf02e431a4d4)) # 1.x Releases[](#1x-releases) * * * * ### 1.0.0[](#100) October 24, 2022[code changes](https://github.com/customerio/customerio-reactnative/issues/13) ### Features * android updates in package ([#13](https://github.com/customerio/customerio-reactnative/issues/13)) ([b708fce](https://github.com/customerio/customerio-reactnative/commit/b708fcea40ae7a6586742fc39ec1c2e06eb7a559)) * android: package updates ([e87c6eb](https://github.com/customerio/customerio-reactnative/commit/e87c6eb05f5d357a626e982702dc1ff7171e4e09)) * android: setup config with initialization ([e915343](https://github.com/customerio/customerio-reactnative/commit/e915343be1e9967e3d3f5214b2ebcf313486c531)) * creating customerio react native package ([#1](https://github.com/customerio/customerio-reactnative/issues/1)) ([2d2bdae](https://github.com/customerio/customerio-reactnative/commit/2d2bdae0af337a488300e9196dcac07b058fe3d2)) * device attributes and other configurable properties ([#4](https://github.com/customerio/customerio-reactnative/issues/4)) ([bd79d96](https://github.com/customerio/customerio-reactnative/commit/bd79d96ecfd8724e979241d6f58324d26b5e1748)) * identify and clear user identity ([#2](https://github.com/customerio/customerio-reactnative/issues/2)) ([4430b66](https://github.com/customerio/customerio-reactnative/commit/4430b66fc6491a4ba7e1c571eb357a318366af3f)) * in-app functionality in react native package ([488c0c0](https://github.com/customerio/customerio-reactnative/commit/488c0c06b852a220c13c9bbffb3d5b254c7fb9ff)) * update package version ([8526a69](https://github.com/customerio/customerio-reactnative/commit/8526a6979405d19922a18f0bf281298b850f3e27)) * updated to android sdk 3.0.0-alpha.2 ([91978d7](https://github.com/customerio/customerio-reactnative/commit/91978d78cb5b10c90caeb088af2fc4f2a15e952b)) * updating ios sdk version in podspec ([1b1c26f](https://github.com/customerio/customerio-reactnative/commit/1b1c26fdedd14e837df2d117a6ab59a4916dc227)) * user-agent updates in package ([efff4fc](https://github.com/customerio/customerio-reactnative/commit/efff4fc30d3050e410b5e56cdd2464b7c2f6de41)) ### Bug Fixes * added support for android sdk 3.1.0 ([44a1b91](https://github.com/customerio/customerio-reactnative/commit/44a1b91c1ccdedecea526c09ff04654333137daa)) * change in way to update config in ios ([#15](https://github.com/customerio/customerio-reactnative/issues/15)) ([8680b28](https://github.com/customerio/customerio-reactnative/commit/8680b2850cf6f1823514ece8205ac8b354d17c04)) * initialized sdk from storage using context ([e3e609a](https://github.com/customerio/customerio-reactnative/commit/e3e609ae2fee6183050d993624bd4bd287a7ec97)) * push notifications integration ([#10](https://github.com/customerio/customerio-reactnative/issues/10)) ([5d7752d](https://github.com/customerio/customerio-reactnative/commit/5d7752d9732bd591be5153df6c5e46aed615bb04)) * updating gist dependency version ([9f8ac3f](https://github.com/customerio/customerio-reactnative/commit/9f8ac3f1d0ea0320d94f000a91b1bc482eba2b1d)) ### Reverts * update package version ([e02e6e5](https://github.com/customerio/customerio-reactnative/commit/e02e6e55cc569f1434efa523781e6805c27d4e2b)) * [6.x Releases](#6x-releases) * [6.1](#61x-releases) * [6.1.0](#610) * [6.0](#60x-releases) * [6.0.0](#600) * [5.x Releases](#5x-releases) * [5.3](#53x-releases) * [5.3.0](#530) * [5.2](#52x-releases) * [5.2.0](#520) * [5.1](#51x-releases) * [5.1.1](#511) * [5.1.0](#510) * [5.0](#50x-releases) * [5.0.1](#501) * [5.0.0](#500) * [4.x Releases](#4x-releases) * [4.11](#411x-releases) * [4.11.0](#4110) * [4.10](#410x-releases) * [4.10.0](#4100) * [4.9](#49x-releases) * [4.9.0](#490) * [4.8](#48x-releases) * [4.8.3](#483) * [4.8.2](#482) * [4.8.1](#481) * [4.8.0](#480) * [4.7](#47x-releases) * [4.7.0](#470) * [4.6](#46x-releases) * [4.6.0](#460) * [4.5](#45x-releases) * [4.5.2](#452) * [4.5.1](#451) * [4.5.0](#450) * [4.4](#44x-releases) * [4.4.3](#443) * [4.4.2](#442) * [4.4.1](#441) * [4.4.0](#440) * [4.3](#43x-releases) * [4.3.1](#431) * [4.3.0](#430) * [4.2](#42x-releases) * [4.2.7](#427) * [4.2.6](#426) * [4.2.5](#425) * [4.2.4](#424) * [4.2.3](#423) * [4.2.2](#422) * [4.2.1](#421) * [4.2.0](#420) * [4.1](#41x-releases) * [4.1.1](#411) * [4.1.0](#410) * [4.0](#40x-releases) * [4.0.2](#402) * [4.0.1](#401) * [4.0.0](#400) * [3.x Releases](#3x-releases) * [3.9](#39x-releases) * [3.9.1](#391) * [3.9.0](#390) * [3.8](#38x-releases) * [3.8.0](#380) * [3.7](#37x-releases) * [3.7.2](#372) * [3.7.1](#371) * [3.7.0](#370) * [3.6](#36x-releases) * [3.6.0](#360) * [3.5](#35x-releases) * [3.5.4](#354) * [3.5.3](#353) * [3.5.2](#352) * [3.5.1](#351) * [3.5.0](#350) * [3.4](#34x-releases) * [3.4.0](#340) * [3.3](#33x-releases) * [3.3.2](#332) * [3.3.1](#331) * [3.3.0](#330) * [3.2](#32x-releases) * [3.2.1](#321) * [3.2.0](#320) * [3.1](#31x-releases) * [3.1.13](#3113) * [3.1.12](#3112) * [3.1.11](#3111) * [3.1.10](#3110) * [3.1.9](#319) * [3.1.8](#318) * [3.1.7](#317) * [3.1.6](#316) * [3.1.5](#315) * [3.1.4](#314) * [3.1.3](#313) * [3.1.2](#312) * [3.1.1](#311) * [3.1.0](#310) * [3.0](#30x-releases) * [3.0.0](#300) * [2.x Releases](#2x-releases) * [2.5](#25x-releases) * [2.5.1](#251) * [2.5.0](#250) * [2.4](#24x-releases) * [2.4.2](#242) * [2.4.1](#241) * [2.4.0](#240) * [2.3](#23x-releases) * [2.3.3](#233) * [2.3.2](#232) * [2.3.1](#231) * [2.3.0](#230) * [2.2](#22x-releases) * [2.2.1](#221) * [2.2.0](#220) * [2.1](#21x-releases) * [2.1.0](#210) * [2.0](#20x-releases) * [2.0.1](#201) * [2.0.0](#200) * [1.x Releases](#1x-releases) * [1.0](#10x-releases) * [1.0.0](#100) --- ## Whats new > Update to 3.4 **Source:** /integrations/sdk/react-native/whats-new/update-to-3.4 # 3.x -> 3.4 This page explains how to update your SDK install to latest versions that may not require a breaking change. While these changes aren’t breaking—you don’t *need* to make these changes—they will simplify your integration, improve the reliability of your metrics, and improve deep link handling on iOS devices. # Upgrade from 3.3 to 3.4+ As of version 3.4, the Customer.io SDK automatically registers push device tokens to identified people and handles push clicks. These features simplify your SDK integration while improving compatibility with apps that use multiple push SDKs. After you install a version of the SDK that is `3.4` or higher, follow these steps to upgrade.  Do you have a swift app? Skip ahead! If you’ve got a Swift app containing the `AppDelegate.swift` file, ignore the steps below and go to the [Swift upgrade section](#upgrade-34-swift). 1. Open your push notification handler file (In our examples, we call this file `MyAppPushNotificationsHandler.swift`) and review all of the highlighted code below. We’ve highlighted the most relevant lines. ```swift import Foundation import CioMessagingPushAPN import UserNotifications // Delete this line import CioTracking @objc public class MyAppPushNotificationsHandler : NSObject { public override init() {} // Replace these 2 lines @objc(setupCustomerIOClickHandling:) public func setupCustomerIOClickHandling(withNotificationDelegate notificationDelegate: UNUserNotificationCenterDelegate) { // With these 2 lines @objc(setupCustomerIOClickHandling) public func setupCustomerIOClickHandling() { // This line of code is required in order for the Customer.io SDK to handle push notification click events. // We are working on removing this requirement in a future release. // Remember to modify the siteId and apiKey with your own values. // let siteId = "YOUR SITE ID HERE" // let apiKey = "YOUR API KEY HERE" CustomerIO.initialize(siteId: siteId, apiKey: apiKey, region: Region.US) { config in config.autoTrackDeviceAttributes = true } // Delete these 2 lines: let center = UNUserNotificationCenter.current() center.delegate = notificationDelegate } // Delete this function: @objc(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:) public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let handled = MessagingPush.shared.userNotificationCenter(center, didReceive: response, withCompletionHandler: completionHandler) // If the Customer.io SDK does not handle the push, it's up to you to handle it and call the // completion handler. If the SDK did handle it, it called the completion handler for you. if !handled { completionHandler() } } } } ``` 2. Open your `AppDelegate.h` file and review all of the highlighted code below. APN #### APN[](#APN) ```Objective-C #import #import #import // Delete this line // Remove `UNUserNotificationCenterDelegate` from this line: @interface AppDelegate: RCTAppDelegate // After this change, the line will look like this: @interface AppDelegate: RCTAppDelegate @end ``` FCM #### FCM[](#FCM) ```Objective-C #import #import #import #import // Delete this line // Remove `UNUserNotificationCenterDelegate` from this line: @interface AppDelegate: RCTAppDelegate // After this change, the line will look like this: @interface AppDelegate: RCTAppDelegate @end ``` 3. Open your `AppDelegate.m` file and review all of the highlighted code below. ```Objective-C - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... // Replace this line [pnHandlerObj setupCustomerIOClickHandling:self]; // With this line: [pnHandlerObj setupCustomerIOClickHandling]; return YES; } - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler { // Remove the line below: [pnHandlerObj userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:completionHandler]; } ``` 4. Now that your app’s code has been simplified, [follow the latest push notification](/integrations/sdk/react-native/push-notifications/push/#integrate-push-capabilities-in-your-app) setup documentation to enable these new features. ### Upgrade from 3.3 to 3.4+, for Swift[](#upgrade-34-swift) 1. Open your `AppDelegate.swift` file and review all of the highlighted code below. We’ve highlighted the most relevant lines. ```swift import CioTracking import CioMessagingPushAPN class AppDelegate: NSObject, UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { CustomerIO.initialize(siteId: "YOUR SITE ID", apiKey: "YOUR API KEY", region: Region.US, configure: nil) // Delete this line UIApplication.shared.registerForRemoteNotifications() return true } } // Delete this function func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { MessagingPush.shared.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) } // Delete this function func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { MessagingPush.shared.application(application, didFailToRegisterForRemoteNotificationsWithError: error) } ``` 2. Now that your app’s code has been simplified, it’s time to enable these new SDK features. To do this, you’ll need to initialize the `MessagingPush` module. [Follow the latest push notification setup documentation](/integrations/sdk/react-native/push-notifications/push/#integrate-push-capabilities-in-your-app) to learn how to do this. --- ## Whats new > Update to 3x **Source:** /integrations/sdk/react-native/whats-new/update-to-3x # 2.x -> 3.x This page details breaking changes from previous versions, so you understand the development effort required to update your app and take advantage of the latest features. ## Versioning[](#versioning) We try to limit breaking or significant changes to major version increments. The three digits in our versioning scheme represent major, minor, and patch increments respectively. [![sdk versioning scheme](https://customer.io/images/sdk-versions.jpg)](#3d36cb047c69017925ca2f80fd8ee72e-lightbox) * **Major**: may include breaking changes, and generally introduces significant feature updates. * **Minor**: may include new features and fixes, but won’t include breaking changes. You may still need to do some development to use new features in your app. * **Patch**: Increments represent minor fixes that should not require development effort. ## Upgrade from 2.x to 3.x[](#upgrade-from-2x-to-3x) Installing and updating our React Native SDK got easier. After you install the CustomerIO React Native SDK version 3.x, open your `ios/Podfile` and follow all 5 steps shown in this code block below: ```ruby # 1. This line is required by the FCM SDK. If you encounter problems during 'pod install', add this line to your Podfile and try 'pod install' again. use_frameworks! :linkage => :static target 'YourApp' do # Note: 'YourApp' is unique to your app. This is here for example purposes, only. # 2. Remove all 'pod CustomerIO...' lines (such as the example below). pod 'CustomerIO/MessagingPushAPN', '~> 2' # Remove me # 3. Add one of these new lines below: # If you use APN for your push notifications on iOS, install the APN pod: pod 'customerio-reactnative/apn', :path => '../node_modules/customerio-reactnative' # If you use FCM for your push notifications on iOS, install the FCM pod: pod 'customerio-reactnative/fcm', :path => '../node_modules/customerio-reactnative' end target 'NotificationServiceExtension' do # 4. Remove all 'pod CustomerIO...' lines (such as the example below). pod 'CustomerIO/MessagingPushAPN', '~> 2' # Remove me pod 'FirebaseMessaging' # Remove me, unless you need to specify a specific version pod 'Firebase' # Remove me, unless you need to specify a specific version. # 5. Add one of these new lines below: # ⚠️ Important: Notice these lines of code include "-richpush" in it making it unique to the host app target above. # If you use APN for your push notifications on iOS, install the APN pod: pod 'customerio-reactnative-richpush/apn', :path => '../node_modules/customerio-reactnative' # If you use FCM for your push notifications on iOS, install the FCM pod: pod 'customerio-reactnative-richpush/fcm', :path => '../node_modules/customerio-reactnative' end ``` **After you modify your `Podfile`**, run the command `pod update --repo-update --project-directory=ios` to make your changes to `ios/Podfile` go into effect. ## Upgrade from 1.x to 2.x[](#upgrade-from-1x-to-2x) ### Rich push initialization(iOS)[](#rich-push-initializationios) If you [followed our docs to setup rich push](/integrations/sdk/react-native/rich-push/) in your app, you should have a `Notification Service Extension` file in your code base. Due to the behavior of Notification Service Extensions in iOS, you need to initialize the Customer.io SDK in your *Notification Service* Extension. In the case that you use Objective-C, you must add the code snippet below into the Swift handler file that you created in *NotificationService* Extension. ```swift class NotificationService: UNNotificationServiceExtension { override func didReceive( _ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void ) { // Make sure to initialize the SDK at the top of this function. CustomerIO.initialize(siteId: "YOUR SITE ID", apiKey: "YOUR API KEY", region: Region.US) { config in config.autoTrackPushEvents = true } ... } } ``` See [our docs for rich push](/integrations/sdk/react-native/rich-push/) to learn more about rich push setup, SDK initialization, and SDK configuration. ### Firebase users must manually install Firebase dependencies[](#firebase-users-must-manually-install-firebase-dependencies) We removed all Firebase SDKs as dependencies from the `CustomerIO/MessagingPushFCM` Cocoapod. If you send messages to your iOS app using FCM, you’ll need to install the Firebase Cloud Messaging (FCM) dependencies in your `Podfile` on your own. ```ruby pod 'Firebase' pod 'FirebaseMessaging' ``` ### We fixed a bug in our iOS modules that may impact your data[](#we-fixed-a-bug-in-our-ios-modules-that-may-impact-your-data) SDK functions that let you send custom data—`trackEvent`, `screen`, `identify` and `deviceAttribute` calls—may have been impacted by a bug in our iOS v1 modules that converted keys in your custom data to `snake_case`. This bug is fixed in v2 of the SDK. You will see your data in Customer.io exactly as you pass it to the SDK. *This bug didn’t surface with all data; it did not affect you if you already snake-cased your data; and it did not affect your Android users.*. ```Javascript // If you passed in custom attributes using camelCase keys: data = {"firstName": "Dana"} // The SDK v1 may have converted this data into: data = {"first_name": "Dana"} // Or, if you used a different format that was not snake_case: data = {"FIRSTNAME": "Dana"} // The SDK v1 may have converted this data into: data = {"f_irstname": "Dana"} ``` You don’t *need* to do anything before you update. But we strongly recommend that you go to **Data & Integrations** > **Data Index** and audit your *attributes* and *events* to determine if the v1 SDK reshaped your data. Make sure that updating to the 2.x SDK won’t impact your segments, campaigns, etc by sending data in a different (but expected) format to Customer.io. [![use the data index to audit events and attributes](https://customer.io/images/data-index-mistake.png)](#84dfc357abd5fbfb83be2ed7dbfd96f3-lightbox) **If your data was affected, you can either:** 1. [(Recommended) Update your attributes, segments, and other information stored in Customer.io to use your original data format.](#v2-option-1) 2. [Set your app to continue using the snake-cased data passed by the 1.x SDK.](#v2-option-2) #### Option 1 (Recommended): Update your data in Customer.io[](#v2-option-1) ##### For Events: `trackEvent` and `screen` calls[](#for-events) Unfortunately, you can’t modify past events sent by `trackEvent` or `screen` calls. But, before you move forward with the 2.0 SDK, you can can update your segments, campaigns, and other Customer.io assets to use your original, not-reshaped data format. For [segmentsA group of people who match a series of conditions. People enter and exit the segment automatically when they match or stop matching conditions.](/journeys/data-driven-segments/), you should use **OR** conditions with the bugged, snake-cased format and your preferred data format. This ensures that people enter your segments and campaigns whether they use your app with the 1.x or 2.x SDKs. [![set up OR conditions in segments](https://customer.io/images/ios-segment-migration.png)](#772278f8216fb5a3503d7d6ee73a09fe-lightbox) ##### For Attributes: `identify`, `profileAttributes`, and `deviceAttribute` calls[](#for-attributes) If your customer data was inappropriately snake-cased by the v1 SDK, you can set up a campaign to apply correctly formatted attributes in Customer.io so you don’t need to update your app! If you update your data this way, you may still need to update segments and other assets to use the correct data shape. 1. Create a segment of people possessing the affected, snake-cased attributes. 2. Create a campaign using this segment as a trigger. 3. In the workflow, add two a *Create or Update Person* actions. [![set up a campaign to update attributes](https://customer.io/images/ios-snake-case-campaign.png)](#66ca68af2a7537e609e3b2935c53bdab-lightbox) 4. Configure the first action to set correctly formatted attributes using the values from your previously-misshaped attributes. Use liquid to identify the attributes in question. Use a liquid or JS *if* statement to set an attribute value if it exists, otherwise your campaign may experience errors. ```fallback {% if customer.snake_case %}{{customer.snake_case}}{% endif %} ``` [![set up an action to update attributes](https://customer.io/images/ios-snake-case-update.png)](#dee0192f2f57443e1ce3e8835f160e0a-lightbox) 5. Configure the second *Create or Update Person* action to remove the bugged, snake-case attributes from your audience. [![set up an action to remove misshaped attributes](https://customer.io/images/ios-snake-case-delete.png)](#321a67ffd5ced3e073f7f3ac761f93ef-lightbox) 6. Make sure that your [segmentsA group of people who match a series of conditions. People enter and exit the segment automatically when they match or stop matching conditions.](/journeys/data-driven-segments/), filters, and other items that might be based on people’s attributes or device attributes are all set to use your preferred format. #### Option 2: Use snake-cased formats in your app[](#v2-option-2) ```Javascript // Call the Customer.io SDK and provide custom attributes like this: CustomerIO.identify("[email protected]", {"first_name": "Dana"}) // Consider sending duplicate data with snake_case CustomerIO.identify("[email protected]", { "firstName": "Dana", // Attribute used with v1 of the SDK that got converted to snake_case. Keeping it here as the bug has been fixed. "first_name": "Dana" // Adding this duplicate attribute for backwards compatibility with customers using old versions of your app. }) ``` Then, after you have determined that all of your app’s customers have updated their app to a version of your app no longer using v1 of the Customer.io SDK, you can remove this duplication: ```Javascript CustomerIO.identify("[email protected]", { "firstName": "Dana" // We can remove the snake_case attribute and go back to just camelCase! }) ``` ---