# iOS SDK Documentation

Generated on: 4/17/2026

This documentation bundle contains the complete iOS SDK documentation for use with LLMs and offline reference.

---

## Quick start guide

**Source:** /integrations/sdk/ios/quick-start-guide

# Quick Start Guide

This guide contains the minimum steps you’ll need to follow to install and start using the Customer.io SDK in your iOS app.

 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)

Our SDK lets you build native mobile apps for iOS. When you’re done, you’ll be able to `identify` people, `track` their activity, and send both push notifications and in-app messages.

1.  [Install and initialize the SDK](#1-install-and-initialize-the-sdk)
2.  [Identify and Track](#2-identify-and-track)
3.  [Add push notification support](#3-add-push-notification-support)
4.  [Add in-app messaging support](#4-add-in-app-messaging-support)

## 1\. Install and initialize the SDK[](#1-install-and-initialize-the-sdk)

1.  Add a [new *iOS* connection in Customer.io](https://fly.customer.io/workspaces/last/journeys/integrations/all/overview) to get your CDP API key. See [Get your CDP API key](../getting-started/auth/#get-your-cdp-api-key) for details. You’ll use this API key to initialize the SDK.
    
2.  Install the SDK. We typically recommend that you install the SDK using Swift Package Manager (SPM). But if your app uses CocoaPods, you can install our pods.
    
     Swift Package Manager (SPM)
    
    #### Swift Package Manager (SPM)[](#Swift Package Manager \(SPM\))
    
    1.  Follow **[Apple’s instructions](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app)** to add `https://github.com/customerio/customerio-ios.git` as a dependency to your project in Xcode and select all the packages you need.
        
    2.  Set the *Dependency Rule* to *Up to Next Major Version*. While we encourage you to keep your app up to date with the latest SDK, major versions can include breaking changes or new features that require your attention.
        
        [![select up to next major version when installing the sdk](https://customer.io/images/xcode-install-sdk.png)](#fa35c2aacc54920cd1a52e6b21c39b49-lightbox)
        
    
     CocoaPods
    
    #### CocoaPods[](#CocoaPods)
    
    Add the pods to your Podfile:
    
    ```ruby
       pod 'CustomerIO/DataPipelines'  # Required
       pod 'CustomerIO/MessagingPushAPN'  # Optional, for APNs
       pod 'CustomerIO/MessagingPushFCM'  # Optional, for FCM
       pod 'CustomerIO/FirebaseWrapper'   # Required for FCM push notifications
       pod 'CustomerIO/MessagingInApp'  # Optional, for in-app messaging
       
    ```
    
3.  Import the appropriate packages. We’re importing all our packages, but you can modify this list if you don’t intend to use all Customer.io features. If you send notifications using Firebase Cloud Messaging (FCM), import the `CioMessagingPushFCM` and `CioFirebaseWrapper` package rather than `CioMessagingPushAPN`.
    
     UIKit with Swift
    
    #### UIKit with Swift[](#UIKit with Swift)
    
    ```swift
     import CioDataPipelines
     import CioMessagingInApp
     import CioMessagingPushAPN
     import UIKit
    ```
    
     UIKit with Objective-C
    
    #### UIKit with Objective-C[](#UIKit with Objective-C)
    
    ```objc
     @import UIKit;
     @import CioDataPipelines;
     @import CioMessagingInApp;
     @import CioMessagingPushAPN;
    ```
    
     SwiftUI
    
    #### SwiftUI[](#SwiftUI)
    
    ```swift
     import SwiftUI
     import CioDataPipelines
     import CioMessagingInApp
     import CioMessagingPushAPN
    ```
    
4.  Initialize the SDK. You’ll usually do this in the `AppDelegate application(_ application: didFinishLaunchingWithOptions)` function, and you’ll use the `CioAppDelegateWrapper` to handle push notifications and device token registration and initialize the SDK. You also need the **CDP API Key** that you obtained when you added your iOS integration to your workspace.
    
     UIKit with Swift
    
    #### UIKit with Swift[](#UIKit with Swift)
    
    ```swift
    @main
    class AppDelegateWithCioIntegration: CioAppDelegateWrapper<AppDelegate> {}
    
     class AppDelegate: UIResponder, UIApplicationDelegate {
         func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
             // Override point for customization after application launch.
    
             // Initialize the Customer.io SDK 
             let cdpApiKey = "YOUR_CDP_API_KEY"
             let siteId = "YOUR_SITE_ID"
             let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
                 // If your account is in the EU region, uncomment the next line
                 // .region(.EU)
                 .migrationSiteId(siteId) // only required if you used version 2.x or earlier
                 .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
                 .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
             CustomerIO.initialize(withConfig: config.build())
    
             return true
         }
     }
     ```
     
    ```
    
     UIKit with Objective-C
    
    #### UIKit with Objective-C[](#UIKit with Objective-C)
    
    ```objc
    @interface AppDelegate : UIResponder <UIApplicationDelegate>
    @property (strong, nonatomic) UIWindow *window;
    @end
    
    // AppDelegate.m
    #import "AppDelegate.h"
    
    @implementation AppDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Initialize the Customer.io SDK
        NSString *cdpApiKey = @"YOUR_CDP_API_KEY";
        NSString *siteId = @"YOUR_SITE_ID";
        
        CioSDKConfigBuilder *config = [[CioSDKConfigBuilder alloc] initWithCdpApiKey:cdpApiKey];
        // If your account is in the EU region, uncomment the next line
        // [config region:CioRegionEU];
        [config migrationSiteId:siteId]; // only required if you used version 2.x or earlier
        [config autoTrackUIKitScreenViews]; // Set auto tracking of UIKit screen views
        [config logLevel:CioLogLevelDebug]; // Add this to troubleshoot issues - disable debug in production
        
        [CustomerIO initializeWithConfig:[config build]];
        
        return YES;
    }
    
    @end
    ```
    
     SwiftUI
    
    #### SwiftUI[](#SwiftUI)
    
    ```swift
    @main
    struct MainApp: App {
        @UIApplicationDelegateAdaptor(CioAppDelegateWrapper<AppDelegate>.self) private var appDelegate
    
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    
    class AppDelegate: UIResponder, UIApplicationDelegate {
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            // Initialize the Customer.io SDK 
            let cdpApiKey = "YOUR_CDP_API_KEY"
            let siteId = "YOUR_SITE_ID"
            let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
                // If your account is in the EU region, uncomment the next line
                // .region(.EU)
                .migrationSiteId(siteId) // only required for migration
                .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
                .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
            CustomerIO.initialize(withConfig: config.build())
    
            return true
        }
    }
    ```
    

## 2\. Identify and Track Users[](#2-identify-and-track-users)

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.
    
     Swift
    
    #### Swift[](#Swift)
    
    ```swift
    // Identify a user with just a user ID
    CustomerIO.shared.identify(userId: "user@example.com")
    
    // Identify a user with additional attributes
    CustomerIO.shared.identify(
     userId: "user@example.com", 
     traits: [
         "email": "user@example.com",
         "first_name": "John",
         "last_name": "Doe",
         "plan_name": "Premium",
         "device_type": "iOS"
     ]
    )
    
    // clear the user identity when they log out
    CustomerIO.shared.clearIdentify()
    ```
    
     Objective-C
    
    #### Objective-C[](#Objective-C)
    
    ```objc
    // Identify a user with just a user ID
    [CustomerIO identifyUserWithId:@"user@example.com"];
    
    // Identify a user with additional attributes
    [CustomerIO identifyUserWithId:@"user@example.com" traits:@{
         @"email": @"user@example.com",
         @"first_name": @"John",
         @"last_name": @"Doe",
         @"plan_name": @"Premium",
         @"device_type": @"iOS"
    }];
    
    // clear the user identity when they log out
    [CustomerIO clearIdentify];
    ```
    
2.  Track a custom activity using the CustomerIO.track method. Events help you trigger personalized campaigns and record user behavior in your app.
    
     Swift
    
    #### Swift[](#Swift)
    
    ```swift
    // Track a simple event without properties
    CustomerIO.shared.track(name: "checkout_started")
    
    // Track an event with properties
    CustomerIO.shared.track(
         name: "product_viewed",
         properties: [
             "product_id": "SKU-123",
             "product_name": "Premium Widget",
             "price": 99.99,
             "currency": "USD",
             "category": "Electronics"
         ]
    )
    ```
    
     Objective-C
    
    #### Objective-C[](#Objective-C)
    
    ```objc
    // Track a simple event without properties
    [CustomerIO trackEventWithName:@"checkout_started"];
    
    // Track an event with properties
    [CustomerIO trackEventWithName:@"product_viewed" properties:@{
         @"product_id": @"SKU-123",
         @"product_name": @"Premium Widget",
         @"price": @99.99,
         @"currency": @"USD",
         @"category": @"Electronics"
    }];
    ```
    

## 3\. Add push notification support[](#3-add-push-notification-support)

In your AppDelegate, after your `CustomerIO.initialize` call, add the `MessagingPushAPN.initialize` call. Our push package has its own settings so you can configure push behaviors.

These instructions are for Apple’s Push Notification service (APNs). If you send push notifications using Firebase Cloud Messaging (FCM), see our [push notification instructions](/integrations/sdk/ios/push/push-setup).

 Swift

#### Swift[](#Swift)

```Swift
    // Initialize the push package    
    MessagingPushAPN.initialize(
        withConfig: MessagingPushConfigBuilder()
            .autoFetchDeviceToken(true)    // Automatically fetch device token and upload to CustomerIO
            .autoTrackPushEvents(true)     // Automatically track push metrics
            .showPushAppInForeground(true) // Enable Notifications in the foreground
            .build()
    )
    
    // Request permission to show notifications
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
        // Process user response here
    }
```

 Objective-C

#### Objective-C[](#Objective-C)

```objc
// Initialize the push package
[MessagingPushAPN initializeWithConfig:[[MessagingPushConfigBuilder new]
    autoFetchDeviceToken:YES    // Automatically fetch device token and upload to CustomerIO
    autoTrackPushEvents:YES     // Automatically track push metrics
    showPushAppInForeground:YES // Enable Notifications in the foreground
    build]];

// Request permission to show notifications
[[UNUserNotificationCenter currentNotificationCenter] 
    requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) 
    completionHandler:^(BOOL granted, NSError * _Nullable error) {
        // Process user response here
    }];
```

## 4\. Add in-app messaging support[](#4-add-in-app-messaging-support)

In your AppDelegate, after your `CustomerIO.initialize` call, add the `MessagingInApp.initialize` call.

Within the call, you’ll need a **Site ID**, which you can get [from your workspace settings](https://fly.customer.io/workspaces/last/settings/api_credentials). This tells the SDK which workspace your in-app messages come from.

 Swift

#### Swift[](#Swift)

```Swift
// Initialize the in-app package
// Change region to .EU if you're in our European Union data center!
MessagingInApp
    .initialize(withConfig: MessagingInAppConfigBuilder(siteId: siteId, region: .US).build())
```

 Objective-C

#### Objective-C[](#Objective-C)

```objc
// Initialize the in-app package
[MessagingInApp initializeWithConfig:[[MessagingInAppConfigBuilder alloc] initWithSiteId:siteId region:CioRegionUS]];
```

---

## Getting started > Auth

**Source:** /integrations/sdk/ios/getting-started/auth

# Authentication

To use the SDK, you’ll need to get two kinds of keys: A CDP *API Key* to send data to Customer.io and a *Site ID*, telling the SDK which workspace your messages come from.

To get your SDK keys and send data to the right places, you’ll need to set up your app as a data inAn integration that feeds data *into* Customer.io. integration in Customer.io, and route it to your workspace. The SDK lets you route data to any number of destinations, but you *must* connect it to your workspace destination to send data, like the people you identify, the events you track, and so on, to Customer.io.

**If you haven’t already set up your app as an integration in Customer.io, [do that first](#set-up-a-new-source).**

## API Keys you’ll need[](#api-keys-youll-need)

1.  **API Key**: This key, shown in code samples as `cdpApiKey`, lets you send data to Customer.io. You’ll need it to initialize the SDK. You’ll get this key when you set up your integration in Customer.io.
2.  **Site ID**: This key tells the SDK which workspace your messages come from. You’ll use it to initialize the `CioMessagingInApp` package and send in-app messages from your workspace. If you’re upgrading from a previous version of the Customer.io SDK, it also serves as the `migrationSiteId`.

## Get your API Key[](#get-your-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 mobile app’s integration card in Customer.io. If you haven’t already set up your Android integration in Customer.io, you’ll need to [do that first](#set-up-a-new-source).

1.  Go to *Data & Integrations* > *Integrations*.
2.  Select your iOS integration in the *Overview* tab. If you don’t see an iOS integration, you’ll need to [set it up](#set-up-a-new-source).
    
    [![the connections page, showing an ios source connected to a journeys destination](https://customer.io/images/cdp-ios-connected-destination.png)](#dbfe96b6f4b8307305cff63d8e4e4ef7-lightbox)
    
3.  Go to **Settings** and find your **API Key**. Copy this key into your initialization call. If you’re upgrading from a previous version of the SDK, you should keep the `siteId` that you used in previous versions as the `migrationSiteId` in your config.
    
    [![get your CDP API Key from your source's settings page](https://customer.io/images/cdp-ios-source-api-key.png)](#82a2f77475b40697711104e1169f8f8b-lightbox)
    
    ```swift
     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
         ...
    
         let config = SDKConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY")
             .migrationSiteId(YOUR_SITE_ID)
             .autoTrackDeviceAttributes(true)
    
         CustomerIO.initialize(withConfig: config.build())
     }
    ```
    

 You’re not done yet

You still need your [Site IDEquivalent to the *user name* you’ll use to interface with the Journeys Track API; also used with our JavaScript snippets. You can find your Site ID under *Workspace Settings* > *API Credentials*](https://fly.customer.io/env/last/settings/api_credentials) to initialize the `CioMessagingInApp` package and to support people updating your app from a previous version of Customer.io SDK. See [Get your Site ID](#get-your-site-id) below.

### Set up 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 data inAn integration that feeds data *into* Customer.io. integration in Customer.io. The “integration” represents your app and the stream of data that you’ll send to Customer.io.

1.  Go to *Data & Integrations* > *Integrations* and click **Add Integration**.
2.  Select the **iOS** integration.
    
    [![set up your iOS source](https://customer.io/images/cdp-ios-source.png)](#67f0591daae0ccd9a402e4bfcb137c78-lightbox)
    
3.  Enter a *Name* for your integration, like “My iOS 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; you’ll use it in your initialization call.
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-ios-source-setup.png)](#be5c9653f8ae715bc7a9a87b2729f76c-lightbox)
    

Now the integrations *Overview* page shows that your iOS app is connected to your workspace. You can also [connect your iOS app to any number of destinations](/integrations/data-out/add-destination/) if you want to send your mobile data to additional services—like your analytics provider, data warehouse, or CRM.

[![the connections page, showing an ios source connected to a journeys destination](https://customer.io/images/cdp-ios-connected-destination.png)](#dbfe96b6f4b8307305cff63d8e4e4ef7-lightbox)

## Get your Site ID[](#get-your-site-id)

You’ll use your Site ID to initialize the `CioMessagingInApp` package and send in-app messages from your workspace.

If you’re upgrading from a previous version, my can also set 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 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**.
    
    [![find your site ID](https://customer.io/images/cdp-js-site-id.png)](#64d2b27827ffddb00dc77b851a7a6854-lightbox)
    
3.  You’ll use this key to initialize the `CioMessagingInApp` package.
    
    ```swift
     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
         ...
    
         let siteId = "YOUR_SITE_ID"
    
         let config = SDKConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY")
             .migrationSiteId(siteId)
             .autoTrackDeviceAttributes(true)
    
         CustomerIO.initialize(withConfig: config.build())
    
         MessagingInApp
             .initialize(withConfig: MessagingInAppConfigBuilder(siteId: siteId, region: .US).build())
             .setEventListener(self)
     }
    ```
    

## 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/ios/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 iOS SDK is a data inAn integration that feeds data *into* Customer.io. in Customer.io. You can route data from your app to both Customer.io *and* other destinations. This makes it easier to use your app as a part of your larger data stack without relying on extra services or code.

When you set up your integration in Customer.io, you’ll determine where you want to route your data to—your workspace *and* destinations outside of Customer.io.

## Minimum support requirements[](#minimum-support-requirements)

To support the Customer.io SDK, you must:

*   Set iOS 13 or later as your minimum deployment target in XCode
    
*   Have an iOS 13+ device to test your implementation. You cannot test push notifications in a simulator.
    

## Objective-C support[](#objective-c-support)

Our SDK is written in Swift and tested in a Swift environment.

Our iOS SDK may work in Objective-C-based projects, but we haven’t tested it that way. If you use our SDK in an Objective-C project and run into trouble, please [let us know](mailto:win@customer.io).

## 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/ios/getting-started/packages-options

# Packages and Configuration Options

The SDK consists of a few packages. You’ll get the most value out of Customer.io when you use all our packages together, but this lets you omit packages for features you don’t intend to use.

## SDK packages[](#sdk-packages)

To minimize our SDK’s impact on your app’s size, we’ve split the SDK into packages. You can limit your install to the packages that you need for your project.

You must install the *CioDataPipelines* package. It lets you identify people, which you must do before you can send them messages, track their events, etc.

Package Product

Required?

Description

CioDataPipelines

✅

`identify` people and `track` events

CioMessagingPushAPN

Receive push notifications over Apple’s Push Notification Service (APNs)

CioMessagingPushFCM

Receive push notifications over Google Firebase Cloud Messaging (FCM)

CioFirebaseWrapper

Required for FCM push notifications. Provides Firebase Cloud Messaging integration

CioMessagingInApp

Receive in-app notifications

CioLocation

[Enrich user profiles with accurate device location](/integrations/sdk/ios/tracking/location)

### Configuration options[](#configuration-options)

When you install the SDK via CocoaPods, you can find our packages by replacing the `Cio` in package names with `CustomerIO/`—e.g. `CustomerIO/DataPipelines`. For FCM push notifications, you’ll need both `CustomerIO/MessagingPushFCM` and `CustomerIO/FirebaseWrapper`.

## Configuration options[](#configuration-options-1)

You’ll call configuration options before you initialize the SDK with `SDKConfigBuilder`. When you initialize the SDK, you can pass configuration options. In most cases, you’ll want to stick with the defaults, but you might do things like change the `logLevel` when testing updates to your app.

```swift
import CioDataPipelines

let config = SDKConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY") // Mandatory for all customers
            .migrationSiteId("YOUR_SITE_ID") // Mandatory only for migrating customers
            .autoTrackDeviceAttributes(true)
            .region(.EU) 

CustomerIO.initialize(withConfig: config.build()) 
```

Option

Type

Default

Description

`cdpApiKey`

string

**Required**: the key you'll use to initialize the SDK and send data to Customer.io

`region`

`.EU` or `.US`

`.US`

**Required if your account is in the EU region**. This sets your account region in the format `Region.US`.

`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

`autoTrackUIKitScreenViews`

boolean

`false`

**For UIKit-based apps**: if true, the SDK automatically sends `screen` events for every screen your audience visits.

`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`

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 2.x**: the credential for previous versions of the SDK. This key is used to 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

## 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.

```swift
let config = SDKConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY")
            .apiHost("YOUR_PROXY_HOST")
            .cdnHost("YOUR_CDN_HOST")

CustomerIO.initialize(withConfig: config.build())
```

## visionOS Support[](#visionos-support)

The iOS SDK supports VisionOS. We have a handy sample app that demonstrates how to use the SDK with Vision Pro devices, along with a handy readme, in the [`Apps/VisionOS` directory](https://github.com/customerio/customerio-ios/tree/main/Apps/VisionOS).

[![a preview of the visionOS sample app where you can set traits to test your implementation](https://customer.io/images/visionos-sample-app.png)](#0b4b32a8e0964b9632fb11c770e6d060-lightbox)

We’ve only tested the iOS SDK with visionOS using Swift Package Manager. If you use CocoaPods, everything *might* work, but we can’t guarantee it.

Also, for now, we *only* support Apple’s Push Notification Service (APNS) for visionOS. You won’t be able to send push notifications to Vision Pro devices using Firebase Cloud Messaging (FCM).

---

## Getting started > Swift six

**Source:** /integrations/sdk/ios/getting-started/swift-six

# Swift 6

Swift 6 introduces language changes that can affect how you integrate the Customer.io iOS SDK. This page explains what to expect when your app uses Swift 6.

## General[](#general)

Swift 6 was introduced in 2024 with Xcode 16. It includes language updates focused on safer, more predictable code, including macro improvements, stronger C++ interoperability, and stronger data-race detection through Strict Concurrency.

Strict Concurrency helps you catch concurrency issues at compile time by enforcing actor isolation, `Sendable` requirements, and safer async boundaries. Many iOS apps are still migrating to Swift 6, while others have already completed the transition and run Swift 6 in production.

## Compatibility[](#compatibility)

To maintain the broadest compatibility possible, our iOS SDK does not yet depend on Swift 6. Starting with Customer.io iOS SDK version 4.2.0, we have no known compatibility issues for apps that use Swift 6. Future SDK versions will be built with Swift 6, so if your app is still in the migration process, we encourage you to complete that transition.

The demo apps in the iOS repository now build with Swift 6.2 and enable most optional Swift 6 features, including `MEMBER_IMPORT_VISIBILITY`.

## Quirks[](#quirks)

Some optional Swift 6 features change library behavior. If you enable these features in your app, your integration may differ from the examples in this documentation.

### MEMBER\_IMPORT\_VISIBILITY[](#member_import_visibility)

When you enable this flag, you need additional `import` statements. In practice, anywhere you use `import CioDataPipelines`, you should also add `import CioInternalCommon`. As the name implies, `CioInternalCommon` is not intended for direct app usage. It may bring some Customer.io utility classes into scope, but it should not introduce negative side effects.

The same pattern applies when importing `CioMessagingPushAPN` or `CioMessagingPushFCM`; in both cases, also import `CioMessagingPush`.

---

## Getting started > Troubleshooting

**Source:** /integrations/sdk/ios/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.  **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.

### Examine data in and data out traffic[](#examine-data-in-and-data-out-traffic)

Your integrations in Customer.io have *Data in* and *Data out* tabs showing you both the calls that come in from your SDK and the data we send out to your destination(s) respectively. You can examine these calls to help you debug issues.

If you have a problem, go to *Data & Integrations* > *Integrations* and check:

1.  That your iOS integration is connected to your workspace. If you don’t connect your integration to your workspace, you won’t be able to send messages, etc. (Typically, it’s connected to your workspace by default.)
2.  Your integration’s *Data In* tab to make sure that your app is sending the right data.
3.  If data isn’t sent to your destination, or it appears incorrect in the destination, go to your outgoing integration’s *Data Out* tab. Check the calls there to make sure that we’re sending the right data from your SDK to the destination. **This includes Customer.io**: your workspace is one of the places you’ll send data from your app!

Check out [Troubleshooting integrations](/cdp/getting-started/troubleshooting/) for more help pinpointing issues in your integration.

## Capture logs[](#capture-logs)

1.  **Enable debug logging in your app**: Everywhere you call `CustomerIO.initialize()`, enable the `debug` log level. This includes in the Notification Service Extension that you [setup for rich push](https://customer.io/sdk/ios/push/#rich-push).
    
     You should not use debug mode in your production app. Remember to disable debug logging before you release your app to the App Store.
    
    ```swift
    // During SDK initialization, enable debug logs: 
    CustomerIO.initialize(
    withConfig: SDKConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY")
       .logLevel(.debug)
       .build()
    )
    ```
    
2.  Open the **Console** app (already installed in MacOS). This is a built-in application you can use to view logs produced by the SDK. We recommend that you use Console instead of Xcode to view and capture logs from the SDK because Xcode may not show you all of the logs the SDK generates.
    
    [![Console app after opening](https://customer.io/images/console-after-open.jpg)](#da5ab00dcaa87b8662e807c153f58c03-lightbox)
    
3.  In Console, click *Action > Include Info messages* and *Action > Include Debug messages*. These settings ensure that you’ll see log messages from the SDK.
    
4.  In Console, on the left, select the iOS device that runs your app with the Customer.io SDK. If you don’t see your device listed, plug in your iOS device into your Mac. Try to use a direct connection via the Apple cable; using a USB hub might prevent the device from showing up.
    
    Then, click **Start streaming**. You will see hundreds or even thousands of logs printed to you. Most of these log messages are not relevant. In the next steps, we’ll filter your log to find relevant messages.
    
5.  In the top right search bar, type “CIO” and press *Enter*.
    
    [![Console app after typing CIO](https://customer.io/images/console-type-cio.jpg)](#c8542c0c181ba819a7aff9b32ad35c95-lightbox)
    
6.  Click the dropdown and select *Category*. You will now only see messages sent from the SDK.
    
    [![Console app after selecting category](https://customer.io/images/console-cio-category.jpg)](#9b772e469be957391c639a298451c8e0-lightbox)
    
7.  In the top right, click **Save** to save this filter. The next time you open Console, just click that saved filter along the top of the screen to see Customer.io SDK logs.
    
    [![Console app after saving filter](https://customer.io/images/console-cio-saved-filter.jpg)](#1247ae1e352e4b411fcdb2f5487a6ad6-lightbox)
    
8.  Click any of the log entries on the screen (or *Edit > Select All*), CMD + C, then CMD + P into a text editor on your computer. Save the file as a .txt.
    
9.  Send the file you just saved to our support team at [win@customer.io](mailto:win@customer.io). 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.

### 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.

### 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.

### 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.

### 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.

### Deep links only open in a browser[](#deep-links-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.

You can learn more about setting up universal links [here](/integrations/sdk/ios/push/#universal-links-deep-links). You can easily test universal links using your Notes app. Try adding a link to a note and tap it. If it drives you to your app, then you’ve set things up correctly!

If your links are opening Safari instead of your app, [check this Apple document to troubleshoot](https://developer.apple.com/documentation/technotes/tn3155-debugging-universal-links).

### Universal Link opens in browser instead of app[](#universal-link-opens-in-browser-instead-of-app)

If you click on a push notification sent by Customer.io that contains a Universal Link deep link > click on the push notification > app opens for a moment > then the browser opens the URL, this could be a sign that something is wrong with your app’s Universal Link handling.

The Customer.io SDK sends a request to your app’s app to give your app an opportunity to handle the Universal Link. If your app does not handle the Universal Link, the SDK will open the link in the browser instead. Let’s walk through some troubleshooting steps to try and fix this behavior so the browser does not open.

In our [deep links Universal Links guide](/integrations/sdk/ios/push/deep-links/#universal-links-deep-links), we show a function that is required to be added to your app: `application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool`. Add a `print("Universal Links handle code called.")` statement or Xcode breakpoint to verify that your code in this function does get called.

If you click on a push notification and you *do not* see this print statement or breakpoint hit, verify that the deep link URL is a valid `https` URL and you have followed all of the Apple documentation linked in [our Universal Links guide](/integrations/sdk/ios/push/deep-links/#universal-links-deep-links).

If you *do* see your print statement or breakpoint hit, then your Universal Link URL is valid and is correctly attached to the push notification for the SDK to understand. Next, verify that your app returns `true` from the `application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool` function. If your app returns `false`, the SDK will open the Universal Link in the browser instead of the app.

Lastly, check if there is another SDK interfering with the Customer.io SDK. In some cases, customers have reported instances where Universal Links, despite being correctly configured within your app, may unexpectedly open in a web browser. This can occur due to interactions with third-party SDKs that perform method swizzling inside your app. To address this, consider reviewing the documentation of other SDKs integrated into your app and disabling swizzling as needed.

## 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 > In app event listeners

**Source:** /integrations/sdk/ios/in-app/in-app-event-listeners

# In-app event listeners

When people receive an in-app message, you’ll listen for events to handle the message’s lifecycle—like dismissing the event or taking a custom action.

## 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.

```swift
func messageActionTaken(message: InAppMessage, actionValue: String, actionName: String) {
        CustomerIO.shared.track(name: "in-app action", properties: [
            "delivery-id": message.deliveryId,
            "message-id": message.messageId,
            "action-value": actionValue,
            "action-name": actionName
    ])
}
```

## 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.

```swift
MessagingInApp.shared.dismissMessage()
```

---

## In app > Inbox

**Source:** /integrations/sdk/ios/in-app/inbox

# Notification inbox

Requires v4.2+This feature requires SDK version 4.2 or later. 

 Minimum SDK required: 4.2

Users who don’t have a version of your app with Customer.io SDK version 4.2 or later will not see this feature and related messages. If you send inbox messages to users without the minimum required version, they won’t be *delivered*.

## How it works[](#how-it-works)

Unlike other messages, inbox messages don’t necessarily appear immediately to users, and they don’t disappear when the user dismisses them. Instead, you’ll display these messages through a notification inbox that your audience can access at their leisure.

Customer.io delivers inbox messages as JSON payloads, not fully-rendered messages. The SDK helps you listen for these payloads, but you’ll determine how to display them in your own inbox client.

You can send an inbox message as a part of a [campaign, broadcast](/journeys/send-inbox/), or [transactional message](/journeys/send-inbox-txnl/).

## Get the inbox instance[](#get-the-inbox-instance)

You’ll access inbox functionality through the `inbox` property on the in-app messaging module.

```swift
let inbox = MessagingInApp.shared.inbox
```

## Inbox methods[](#inbox-methods)

The inbox instance provides several methods to manage messages.

Method

Description

`getMessages()`

Get all messages from the inbox. Returns an async array of messages.

`getMessages(topic:)`

Get messages filtered by topic. Returns an async array of messages.

`messages()`

AsyncStream for all messages with real-time updates. Automatically cleans up when task is cancelled.

`messages(topic:)`

AsyncStream for messages filtered by topic. Automatically cleans up when task is cancelled.

`addChangeListener(_:)`

Add a listener to be notified when messages change. Requires manual cleanup.

`addChangeListener(_:topic:)`

Add a listener for messages filtered by topic. Requires manual cleanup.

`removeChangeListener(_:)`

Remove a previously added change listener.

`markMessageOpened(message:)`

Mark a message as opened.

`markMessageUnopened(message:)`

Mark a message as unopened.

`markMessageDeleted(message:)`

Mark a message as deleted.

`trackMessageClicked(message:)`

Track a click on the message without an action name.

`trackMessageClicked(message:actionName:)`

Track a click on the message with an action name.

## Inbox message payloads[](#inbox-message-payloads)

Inbox messages are delivered as a JSON payload. The SDK helps you listen for the payload, but you’ll render the content in your own inbox client.

The client payload includes the following fields, but you’re most concerned with the `properties` object, which represents your message content. By default, we’ll send a `title` and `body` field, but you can add other fields like an `image` or a `link`—whatever you set up your inbox to expect.

Make sure that your team members know what payloads to send—especially if you expect different payloads for different topics or types of messages.

Field

Type

Description

`messageId`

string

Unique identifier for the message.

`sentAt`

string

When the message was sent.

`expiresAt`

string

When the message will expire.

`opened`

boolean

Whether the message has been opened.

`topics`

array

The topics that the message belongs to.

`type`

string

The type of message.

`properties`

object

The properties of the message.

```json
{
    "messageId": "1234567890",
    "sentAt": "2026-02-05T12:00:00Z",
    "expiresAt": "2026-02-05T12:00:00Z",
    "opened": false,
    "topics": ["orders", "shipping"],
    "type": "order_shipped",
    "properties": {
        "title": "Hey Cool Person, your order shipped!",
        "body": "You can track your order #1234567890 here:",
        "link": "https://example.com/orders/1234567890"
    }
}
```

### Inbox topics and types[](#inbox-topics-and-types)

When you send an inbox message, you can assign it to one or more topics. You can use these topics to filter messages when you fetch them. You can also use the topics to determine how to render the messages in your notification inbox.

Messages also have a `type`. Think of this like a sub-category or topic for a message. For example, you might have `orders` and `sale` topics, where `orders` don’t have images but `sale` topics might. Or, within the `orders` topic, you might have `order_placed` and `order_shipped` types, where `order_placed` lists order details and images of purchased products and `order_shipped` provides a link to the tracking information for the order that opens in a new tab.

## Setup your notification inbox[](#setup-your-notification-inbox)

Inbox messages are just JSON payloads. You’ll need to build your own inbox client to display the messages. The code below gives you a starting point, but you can build your own inbox client however you want.

### Get messages[](#get-messages)

```swift
// Get all messages
let messages = await inbox.getMessages()

// Get messages filtered by topic
let promoMessages = await inbox.getMessages(topic: "promo")
```

### Listen for message updates[](#listen-for-message-updates)

The SDK provides two approaches for listening to message updates: **AsyncStream** (modern) and **Listener pattern** (classic).

#### AsyncStream (modern approach)[](#asyncstream-modern-approach)

AsyncStream automatically cleans up when the task is cancelled, making it ideal for SwiftUI views or structured concurrency.

```swift
// Stream all messages
Task {
    for await messages in inbox.messages() {
        // Update your UI with the messages
        updateInboxUI(messages)
    }
}

// Stream messages filtered by topic
Task {
    for await messages in inbox.messages(topic: "promo") {
        // Update your UI with filtered messages
        updatePromotionsUI(messages)
    }
}
```

#### Listener pattern (classic approach)[](#listener-pattern-classic-approach)

The listener pattern requires manual cleanup but works well with UIKit view controllers.

```swift
// Your class must conform to NotificationInboxChangeListener
class InboxViewController: UIViewController, NotificationInboxChangeListener {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Add listener (must be called on MainActor)
        Task { @MainActor in
            inbox.addChangeListener(self)
        }
    }

    // Implement the protocol method
    func onMessagesChanged(messages: [InboxMessage]) {
        // Update your UI with the messages
        updateInboxUI(messages)
    }

    deinit {
        // Remove listener when view controller is deallocated
        inbox.removeChangeListener(self)
    }
}

// Add listener for specific topic
Task { @MainActor in
    inbox.addChangeListener(self, topic: "promo")
}
```

### Mark messages as opened or unopened[](#mark-messages-as-opened-or-unopened)

```swift
// Mark a message as opened
inbox.markMessageOpened(message: message)

// Mark a message as unopened
inbox.markMessageUnopened(message: message)
```

### Track message clicks[](#track-message-clicks)

```swift
// Track a click without an action name
inbox.trackMessageClicked(message: message)

// Track a click with an action name
inbox.trackMessageClicked(message: message, actionName: "view_details")
```

### Delete messages[](#delete-messages)

```swift
// Mark a message as deleted
inbox.markMessageDeleted(message: message)
```

## Working with message properties[](#working-with-message-properties)

You can access message properties to display custom content in your inbox:

```swift
// Access message properties
let title = message.properties["title"] as? String
let body = message.properties["body"] as? String
let link = message.properties["link"] as? String
let imageUrl = message.properties["image"] as? String

// Handle message action when user taps
func handleMessageTap(_ message: InboxMessage) {
    // Mark as opened
    inbox.markMessageOpened(message: message)

    // Track click
    inbox.trackMessageClicked(message: message)

    // Open link if available
    if let link = message.properties["link"] as? String,
       let url = URL(string: link) {
        UIApplication.shared.open(url)
    }
}
```

---

## In app > Inline in app

**Source:** /integrations/sdk/ios/in-app/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)

Add an inline view to your app’s UI

You’ll add a UI element to your app’s UI code to support inline messages using SwiftUI, Storyboard, Interface Builder, or UIKit via code.

 We’ve set up examples in our [UIKit and SwiftUI sample apps](https://github.com/customerio/customerio-ios/tree/main/Apps/) that might help if you want to see a real-world implementation of this feature.

 Storyboard

#### Storyboard[](#Storyboard)

1.  Open your storyboard file and drag a `UIView` onto your view controller.
    
2.  Set the class of the `UIView` to `InlineMessageUIView` in the Identity Inspector.
    
3.  Setup layout constraints: you’re responsible for setting the width and the leading, top, trailing, and bottom constraints for the view. See [view layout](#view-layout) for more information.
    
4.  In your `ViewController`, set the `elementId` for the `InlineMessageUIView`. This is the ID you’ll use in the Customer.io UI when you want to send an in-app message.
    
    ```swift
     import CioMessagingInApp
    
     @IBOutlet weak var inlineInAppView: InlineMessageUIView!
    
     override func viewDidLoad() {
         super.viewDidLoad()
    
         // Replace <element-id-here> with an ID that makes sense to you.
         // You'll use this ID when you build an in-app message in Customer.io.
         inlineInAppView.elementId = "<element-id-here>"
     }
    ```
    

 UIKit Swift

#### UIKit Swift[](#UIKit Swift)

1.  Create an instance of `InlineMessageUIView` and add it to your view hierarchy.
    
    ```swift
    import CioMessagingInApp
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        // Replace <element-id-here> with an ID that makes sense to you.
        // You'll use this ID when you build an in-app message in Customer.io.
        let inlineMessage = InlineMessageUIView(elementId: "<element-id-here>")
    }
    ```
    
2.  Setup layout constraints: you’re responsible for setting the width and the leading, top, trailing, and bottom constraints for the view. See [view layout](#view-layout) for more information.
    

 SwiftUI

#### SwiftUI[](#SwiftUI)

Create an instance of `InlineMessage` and add it to your view hierarchy. You shouldn’t set a height on your View to avoid breaking functionality. `InlineMessage` automatically updates the height for you when messages load and are interacted with.

```swift
import SwiftUI
import CioMessagingInApp

struct MyScreen: View {
    var body: some View {
        // Replace <element-id-here> with an ID that makes sense to you.
        // You'll use this ID when you build an in-app message in Customer.io.
        InlineMessage(elementId: "<element-id-here>")
        // Note: Avoid setting a hard-coded height on the View to avoid breaking functionality. 
        // The InlineMessage dynamically changes it's height when messages are loaded and interacted with.             
        // .frame() 
    }
}
```

### UIKit: Setup layout constraints for your message[](#view-layout)

`InlineMessageUIView` uses AutoLayout and modifies its own height. But otherwise, you’ll need to set up layout constraints for the view including the width and the leading, top, trailing, and bottom constraints for the view.

You can create a height constraint, but it won’t matter because we modify it at runtime. You may still want to set a height if you use Storyboard because XCode will throw warnings and errors if you don’t set a height.

## 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.
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 these steps to implement custom action buttons 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)

For inline in-app messages, you have 2 options for listening to these action click events.

1.  Register a delegate with inline View:
    
     UIKit
    
    #### UIKit[](#UIKit)
    
    ```swift
    import CioMessagingInApp
    
    class MyViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // Given you have an inline View in your ViewController, set a delegate to listen for action events. 
            // Note: The inline View holds a `weak` reference to the `onActionDelegate`. 
            inlineInAppView.onActionDelegate = self
        }
    }
    
    extension MyViewController: InlineMessageUIViewDelegate {
        func onActionClick(message: InAppMessage, actionValue: String, actionName: String) {
            // 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(actionValue)
                    
                // You can add more cases here for other actions
                default:
                    // Handle unknown actions or do nothing
                    print("Unknown action: \(actionName)")
            }
        }
    }
    ```
    
     SwiftUI
    
    #### SwiftUI[](#SwiftUI)
    
    ```swift
    import CioMessagingInApp
    import SwiftUI
    
    struct MyScreen: View {
        var body: some View {
            InlineMessage(elementId: "<element-id-here>", onActionClick: { message, actionValue, actionName in
                // Perform some logic when custom action button pressed. 
    
                // Example code handling button press: 
                switch(actionValue) { // use actionValue or actionName, depending on how you composed the in-app message. 
                    case "enable-auto-renew":
                        // Perform the action for enabling auto-renew
                        enableAutoRenew(actionValue)
                        
                    // You can add more cases here for other actions
                    default:
                        // Handle unknown actions or do nothing
                        print("Unknown action: \(actionName)")
                }
            })
        }
    }
    ```
    
2.  Register a global SDK event listener.
    
    When you register an [event listener](../in-app-event-listeners/#event-listeners) with the in-app 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 if you want.
    

## Handle responses to messages (event listeners)[](#event-listeners)

Similar to [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 `onActionDelegate` 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!

### Known limitations[](#known-limitations)

We’re actively developing this feature. But, in the meantime, you should be aware of the following limitations:

*   **`InlineMessageUIView` does not work as expected inside a `UITableView`**: If you have a scrolling list, `InlineMessageUIView` works great in `UIStackView` and `UIScrollView`.

---

## In app > Set up in app

**Source:** /integrations/sdk/ios/in-app/set-up-in-app

# Set up in-app messaging

Incorporate in-app messages to send dynamic, personalized content to people using your app. With in-app messages, you can speak directly to your app’s users when they use your app.

## How it works[](#how-it-works)

An in-app message is a message that people see within the app; people won’t see your message until they open your app. To set up in app messaging, install and initialize the `CioDataPipelines` and `CioMessagingInApp` packages.

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/ios/tracking/track-events/#auto-screenview) features. Screen tracking tells us the names of your pages and which pages a person is visits, so we can display in-app messages on the correct screens (or “pages”) in your app.

## Set up in-app messaging[](#set-up-in-app-messaging)

Use Swift Package Manager to install the `CioMessagingInApp` package. See [Getting Started](/integrations/sdk/ios/quick-start-guide) for installation instructions. Initializing your app with the `CioMessagingInApp` package sets up your app to receive in-app messages.

```swift
import CioDataPipelines
import CioInternalCommon
import CioMessagingInApp
import UIKit

@main
class AppDelegateWithCioIntegration: CioAppDelegateWrapper<AppDelegate> {}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        var siteId = YOUR_SITE_ID
        
        let config = SDKConfigBuilder(cdpApiKey: YOUR_CDP_API_KEY)
            .autoTrackDeviceAttributes(true)
            .autoTrackUIKitScreenViews()
            .migrationSiteId(siteId)

        CustomerIO.initialize(withConfig: config.build())

        // Initialize messaging features after initializing Customer.io SDK
        MessagingInApp
            .initialize(withConfig: MessagingInAppConfigBuilder(siteId: siteId, region: .US).build())
            .setEventListener(self)

        return true
    }
}   
```

## Anonymous messages[](#anonymous-messages)

As of version 3.14, 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.

## In-app configuration options[](#in-app-configuration-options)

**You must pass both of the following configuration options** when you initialize the `MessagingInApp` package.

Option

Type

Default

Description

`siteId`

string

The [Site IDEquivalent to the *user name* you’ll use to interface with the Journeys Track API; also used with our JavaScript snippets. You can find your Site ID under *Workspace Settings* > *API Credentials*](https://fly.customer.io/env/last/settings/api_credentials) from a set of Track API credentials; this determines the workspace that your app listens for in-app messages from.

`region`

`Region.US` or `Region.EU`

The region your Customer.io account resides in.

---

## In app > Target in app messages

**Source:** /integrations/sdk/ios/in-app/target-in-app-messages

# Page rules

Sending people in-app messages often depends on the screens they visit in your app.

You can set page rules when you create in-app messages. These rules determine the pages that your audience must visit in your app to see each message. Before you can take advantage of page rules, you need to:

1.  Track screens in your app. You can add `$0.autoTrackScreenViews = true` to your `CustomerIO.config` to automatically track screens or you can [track screens manually](/integrations/sdk/ios/tracking/screen-events/#manual-screenview).
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.

The SDK automatically uses the class name of `UIViewController`, minus `ViewController`, as the name of each page. For example, if you wanted to display an in-app message on a class called `EditProfileViewController`, you would enter `EditProfile` as your page rule.

 Make sure your screens use the same names across your apps

If you have a screen called `DashboardActivity` in Android, and `DashboardViewController` in iOS, we’ll recognize `Dashboard` as the screen for both platforms, making it easier for you to set page rules and track events for users across platforms.

[![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. If you’re targeting your mobile app, make sure your page rules match the casing of the `name` in your `screen` events. If you’re targeting your website, your page rules should always be lowercase.

[![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)

---

## Push > App groups

**Source:** /integrations/sdk/ios/push/app-groups

# App Groups for push tracking

Configure App Groups for reliable push delivery tracking. App Groups let the SDK recover delivery metrics that iOS might otherwise discard when it terminates the Notification Service Extension.

App Groups are required for reliable push delivery tracking. Without this setup, delivery metrics may be lost if iOS terminates the Notification Service Extension before the tracking request completes. With App Groups configured, the SDK automatically recovers any lost metrics on the next app launch.

## Before you begin[](#before-you-begin)

Before you configure App Groups, make sure you’ve completed the following:

*   [Set up push notifications](/integrations/sdk/ios/push/push-setup/) in your app
*   [Set up rich push](/integrations/sdk/ios/push/rich-push/) with a Notification Service Extension (NSE)

## 1\. Add the App Group capability in Xcode[](#1-add-the-app-group-capability-in-xcode)

You need to add the App Groups capability to both your main app target and your Notification Service Extension target in Xcode.

 Automatic signing

#### Automatic signing[](#Automatic signing)

If you use automatic signing (the most common setup), this is the only step outside of code. Xcode registers the group and updates provisioning profiles automatically.

1.  In Xcode, select your **main app target** > **Signing & Capabilities** > **\+ Capability** > **App Groups**.
2.  Click **+** and enter your group identifier—for example, `group.com.example.myapp.cio`.
3.  Select your **Notification Service Extension target** > **Signing & Capabilities** > **\+ Capability** > **App Groups**.
4.  Select the **same** App Group you created in step 2.

Both targets must reference the exact same App Group string.

 Manual signing

#### Manual signing[](#Manual signing)

If you use manual signing, you need to register the group in the Apple Developer Portal and regenerate your provisioning profiles.

1.  Sign in to the [Apple Developer Portal](https://developer.apple.com/) and go to **Certificates, Identifiers & Profiles**.
2.  Click **Identifiers** > **+** > **App Groups**.
3.  Enter your identifier. It must start with `group.`—for example, `group.com.example.myapp.cio`.
4.  Under **Identifiers**, select your main app’s App ID, enable **App Groups**, click **Configure**, and select your group.
5.  Repeat step 4 for your Notification Service Extension’s App ID.
6.  Regenerate provisioning profiles for both your main app and Notification Service Extension. Enabling App Groups invalidates your existing provisioning profiles.

 You must regenerate your provisioning profiles in the Apple Developer Portal after enabling App Groups. You don’t need to regenerate certificates.

 Already have an App Group?

You can reuse an existing App Group by passing its identifier to `.appGroupId()`. There’s no conflict in having multiple App Groups on a target.

## 2\. Pass the App Group ID in your SDK configuration[](#2-pass-the-app-group-id-in-your-sdk-configuration)

After you add the App Group capability, pass the App Group ID to the SDK in both your host app and your Notification Service Extension. Both are required—App Groups work by sharing storage between the two targets, so the SDK needs the identifier in each place to read and write delivery metrics. The App Group ID must be identical in both places and must match the entitlements you set up in Xcode.

### Host app initialization[](#host-app-initialization)

 APNs

#### APNs[](#APNs)

```swift
MessagingPushAPN.initialize(
    withConfig: MessagingPushConfigBuilder()
        .appGroupId("group.com.example.myapp.cio")
        .build()
)
```

 FCM

#### FCM[](#FCM)

```swift
MessagingPushFCM.initialize(
    withConfig: MessagingPushConfigBuilder()
        .appGroupId("group.com.example.myapp.cio")
        .build()
)
```

### Notification Service Extension initialization[](#notification-service-extension-initialization)

 APNs

#### APNs[](#APNs)

```swift
MessagingPushAPN.initializeForExtension(
    withConfig: MessagingPushConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY")
        .appGroupId("group.com.example.myapp.cio")
        .build()
)
```

 FCM

#### FCM[](#FCM)

```swift
MessagingPushFCM.initializeForExtension(
    withConfig: MessagingPushConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY")
        .appGroupId("group.com.example.myapp.cio")
        .build()
)
```

 App Group ID must match everywhere

The `.appGroupId()` value must be identical in your host app initialization, your NSE initialization, and the App Group entitlements on both targets. A mismatch prevents the SDK from accessing shared storage.

### Fallback behavior[](#fallback-behavior)

If you omit `.appGroupId(...)`, the SDK attempts to infer the identifier using the convention `group.{bundleId}.cio`. This can work as a fallback, but we recommend explicitly passing the value to avoid configuration issues.

---

## Push > Deep links

**Source:** /integrations/sdk/ios/push/deep-links

# Deep Links

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.

## Set up Universal Links[](#universal-links-deep-links)

To enable Universal Links in your iOS app, follow the instructions [on the Apple documentation website](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app). Be sure to complete all of the steps required including [making modifications to your website to host a new file](https://developer.apple.com/documentation/Xcode/supporting-associated-domains) and [making modifications to your mobile app’s code to handle the deep link](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app).

Depending on how you set up your mobile app (SwiftUI, UIKit, watchOS, etc), you may need to [handle deep links in multiple functions](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) in your code.

### Handling deep links on push notification click[](#handling-deep-links-on-push-notification-click)

Our SDK automatically handles deep links for Customer.io push notifications, calling The SDK’s calls `UIApplication.shared.open()` for the deep link URL by default. You don’t need to process deep links yourself in `userNotificationCenter(:didReceive:withCompletionHandler)`.

But, if you want to control URL handling and open specific screens in your app, you should implement the `application(:continue:restorationHandler:)` method in your AppDelegate.

```swift
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
      _ application: UIApplication, 
      continue userActivity: NSUserActivity, 
      restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
    ) -> Bool {
        guard let universalLinkUrl = userActivity.webpageURL else {
            return false
        }

        // Parse `universalLinkUrl` object to perform the action you want in your app. 

        // return true from this function if your app handled the deep link. 
        // return false from this function if your app did not handle the deep link and you want sdk to open the URL in a browser.
    }
}
```

#### Deep link issue with calling `application(:continue:restorationHandler:)`[](#deep-link-issue)

Some 3rd party SDKs might block calls to the `application(:continue:restorationHandler:)` method. If you encounter this problem, you can use an alternative approach to receive callbacks when your audience clicks notifications with deep links.

```swift
@main
// Add the CioAppDelegateWrapper to handle push notifications and device token registration
class AppDelegateWithCioIntegration: CioAppDelegateWrapper<AppDelegate> {}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        // Step 2: initialize the SDK 
        var cdpApiKey = YOUR_CDP_API_KEY
        var siteId = YOUR_SITE_ID
        
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            .deepLinkCallback { (url: URL) in
                // You can call any method to process this further,
                // or redirect it to `application(_:continue:restorationHandler:)` for consistency, if you are already using it
                let openLinkInHostAppActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
                openLinkInHostAppActivity.webpageURL = url
                return self.application(UIApplication.shared, continue: openLinkInHostAppActivity, restorationHandler: { _ in })
            }

        CustomerIO.initialize(withConfig: config.build())

        // Step 3: Initialize the in-app package
        // Change region to .EU if you're in our European Union data center!
        MessagingInApp.initialize(withConfig: MessagingInAppConfigBuilder(siteId: siteId, region: .US).build())

        // Step 4: Initialize the push package    
        MessagingPushAPN.initialize(withConfig: MessagingPushConfigBuilder().build())

        return true
    }
}   
```

 Check universal links using your Notes app

Try creating a note with a universal link and tapping the link to double-check that the link opens in your app and not in a browser window. This is an easy way to make sure that you’ve set up universal links correctly.

If your links are opening Safari instead of your app, [check this Apple document to troubleshoot](https://developer.apple.com/documentation/technotes/tn3155-debugging-universal-links).

## Setup App Scheme Deep Links[](#app-scheme-deep-links)

1.  Open your Xcode project and go to your project’s settings. Select your app **Target**, click the **Info** tab, and then click **URL Types** > to create a new URL Type.
    
    [![visual of the typed instructions in the sentence above to create a new URL type](https://customer.io/images/xcode_appscheme_urltypes.jpeg)](#bbf5a38ebbb81ce018f4e79f703853b4-lightbox)
    
2.  Enter a unique value for your app for URL Schemes.
    
    [![visual of the typed instructions in the sentence above to enter a unique value for URL scheme](https://customer.io/images/xcode_appscheme_makeurltype.jpeg)](#e9aefb85a68022dce00ef35fabc0ab4f-lightbox)

---

## Push > Provisional

**Source:** /integrations/sdk/ios/push/provisional

# Provisional Push

Provisional push support is available for iOS 15 and above. Customer.io doesn’t have a specific “provisional push” feature. This is just something that iOS supports out of the box with iOS 15+. See Apple’s [provisional authorization documentation](https://developer.apple.com/documentation/usernotifications/unauthorizationoptions/provisional) to learn more.

When you request authorization for push notifications, you can include the `.provisional` option in the `requestAuthorization` call.

```swift
class NotificationUtil: NotificationUtility {
    func showPromptForPushPermission(completionHandler: @escaping (Bool) -> Void) {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound, .provisional], completionHandler: { status, _ in
            completionHandler(status)
        })
    }
}
```

---

## Push > Push certificates

**Source:** /integrations/sdk/ios/push/push-certificates

# Push service certificates

You can send push notifications to iOS devices using either [Apple’s Push Notification service (APNs)](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification) or Google’s Firebase Cloud Messaging (FCM) service.

To authorize your Customer.io workspace to send notifications through APNs, you’ll need to upload your APNs `.p8` certificate and provide your credentials or upload your .JSON to iOS devices over Apple’s Push Notification service, you’ll need to upload your APNs `.p8` certificate and enter your Apple Developer *Key ID*, *Team ID*, and *Bundle ID*.

## Upload your push certificate[](#upload-your-push-certificate)

If you don’t already have your [`.p8` certificate for APNs](#get-your-p8-file-for-apns) or your [`.JSON` file for FCM](#get-your-json-file-for-fcm), you’ll need to get one before you can finish this process and send push notifications.

 APNs

#### APNs[](#APNs)

1.  In Customer.io, go to your workspace’s **Settings** > **Workspace Settings** and click **Settings** next to *Push*.
    
2.  Click **Enable** under *iOS*, and select the *Apple Push Notification service (APNs)* option.
    
3.  Click **Choose file…** and upload your .p8 certificate.
    
4.  Enter your *Key ID*, *Team ID*, and *Bundle ID*. You can find these in your [Apple Developer Account](https://developer.apple.com/).
    
5.  (Optional) Enable the *Send all push notifications to sandbox* option.
    
    Your iOS certificate may have both sandbox and production environments; this option sends push notifications to both environments.
    
    [![image.png](https://customer.io/images/image%28320%29.png)](#1a9ffb2cbe8c4e2106f48cfcc60a845a-lightbox)
    
     We recommend [creating a separate workspace](/journeys/push-qa-testing#testing-push-notifications) for your sandbox environment.
    
6.  Click **Save Changes**
    

[![Push Settings - iOS_provider (APNs)](https://customer.io/images/push_settings_ios_provider_APNs.png)](#f062f3fd15a85996eea68c36da00a1df-lightbox)

 FCM

#### FCM[](#FCM)

1.  In Customer.io, go to [**Settings > Workspace Settings**](https://fly.customer.io/workspaces/last/settings/) and click **Settings** next to *Push*.
    
2.  For *iOS*, click **Enable**, and select the *Firebase Cloud Messaging (FCM)* option.
    

[![Push Settings - iOS_provider (FCM)](https://customer.io/images/push_settings_ios_provider.png)](#85c4ce7cd841a1b35322730d323ee860-lightbox)

## Get your .p8 file for APNs[](#get-your-p8-file-for-apns)

1.  Log into your Apple Developer account and go to **[Certificates, Identifiers & Profiles > Keys](https://developer.apple.com/account/resources/authkeys)**. Click the blue button to create a new key.
2.  Click **Apple Push Notifications service (APNs)** and enter a name for the key.
3.  Click **Continue** and then **Register** to create the key.
4.  Download your keys and put it somewhere you’ll remember. You can only download your key once!

## Get your .JSON file for FCM[](#get-your-json-file-for-fcm)

Before you can get a push certificate for Firebase Cloud Messaging, make sure that the FCM API is enabled for your project. You can [check that here](https://console.developers.google.com/apis/api/fcm.googleapis.com/overview).

1.  Log into the Firebase Console for your project.
    
2.  Click in the sidebar and go to **Project settings**.
    
    [![Access your project settings in firebase](https://customer.io/images/project-settings.png)](#f0e4e4c5092f6c0e9e76463da609a7f8-lightbox)
    
3.  Go to **Service Accounts** and click **Generate New Private Key**. Confirm your choice and download the credential file.
    
    [![Generate a new private key in Firebase](https://customer.io/images/generate-key.png)](#7cde629b570345da48323d76ead58719-lightbox)

---

## Push > Push metrics

**Source:** /integrations/sdk/ios/push/push-metrics

# Push metrics

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.

 Improve delivery metric reliability

Configure [App Groups](/integrations/sdk/ios/push/app-groups/) to make sure delivery metrics aren’t lost when iOS terminates the Notification Service Extension before the tracking request completes. With App Groups, the SDK automatically recovers any undelivered metrics on the next app launch.

If you already configured [rich push notifications](/integrations/sdk/ios/rich-push/), the SDK will automatically track `opened` and `delivered` events for push notifications originating from Customer.io. See section [*Automatic push handling*](#automatic-push-handling) below to learn more about this great feature and how to best take advantage of it.

Otherwise, you can:

*   [Record push metrics with `UserNotifications`](#metrics-userNotifications).
*   [Extract delivery ID and Delivery Token parameters directly](#metrics-direct).

## Automatic push handling[](#automatic-push-handling)

After you call `MessagingPushAPN.initialize` or `MessagingPushFCM.initialize` in your `AppDelegate` and set up the `CioAppDelegateWrapper`, your app is ready to automatically handle push notifications that originate from Customer.io. No additional code is required for your app 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.

#### Configure push behavior[](#configure-push-behavior)

Configure push notification behavior using the `MessagingPushConfigBuilder`:

```swift
MessagingPushAPN.initialize(
    withConfig: MessagingPushConfigBuilder()
        .autoFetchDeviceToken(true)    // Automatically fetch device token and upload to CustomerIO
        .autoTrackPushEvents(true)     // Automatically track push metrics
        .showPushAppInForeground(true) // Enable Notifications in the foreground
        .build()
)
```

#### Configure push display behavior while app in foreground[](#configure-push-display-behavior-while-app-in-foreground)

If your app is in the foreground and receives a push notification, Customer.io will show the notification based on the value of the `showPushAppInForeground` configuration property.

You can choose whether or not to display push notifications when your app is foregrounded by implementing the `userNotificationCenter(_:didReceive:withCompletionHandler:)` method in your AppDelegate. Note that when you implement `UNUserNotificationCenterDelegate`, it takes priority over the `showPushAppInForeground` configuration property.

Add the highlighted code to your `AppDelegate.swift` file for custom handling:

```swift
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
      _ application: UIApplication,
      didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // ... 

        // Set the AppDelegate as a delegate for push notification events: 
        UNUserNotificationCenter.current().delegate = self

        return true
    }
}

extension AppDelegate: UNUserNotificationCenterDelegate {
    // Function called when a push notification arrives while app is in foreground
    func userNotificationCenter(
      _ center: UNUserNotificationCenter, 
      willPresent notification: UNNotification, 
      withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        // To show it, return appropriate options for your case
        completionHandler([.banner, .list, .badge, .sound])

        // To hide it, return empty array
        // completionHandler([])
    }
}
```

#### Custom handling when a Customer.io push is clicked[](#custom-handling-when-a-customerio-push-is-clicked)

Add the highlighted code to your `AppDelegate.swift` file if your app needs to perform custom handling when users click push notifications. For example, you’d need to perform custom handling if you need to process custom data that you attached to the push notification payload.

```swift
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
      _ application: UIApplication,
      didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // ... 

        // Set the AppDelegate as a delegate for push notification events: 
        UNUserNotificationCenter.current().delegate = self

        return true
    }
}

extension AppDelegate: UNUserNotificationCenterDelegate {
    // Function called when a push notification is clicked or swiped away.
    func userNotificationCenter(
      _ center: UNUserNotificationCenter,
      didReceive response: UNNotificationResponse,
      withCompletionHandler completionHandler: @escaping () -> Void
    ) {
        // If you need to know if a push was clicked: 
        let pushWasClicked = response.actionIdentifier == UNNotificationDefaultActionIdentifier

        // Process custom data attached to payload, if you need: 
        let pushPayload = response.notification.request.content.userInfo

        // Important: When you're done processing the push notification, you're required to call the completionHandler. 
        // Even if you do not process a push, you're still required to call the completionHandler() in this function. 
        completionHandler()
    }
}
```

#### Deep links handling when push is clicked[](#deep-links-handling-when-push-is-clicked)

Our SDK handles deep links automatically for notifications originated from Customer.io. You can find more details at [Deep Links](/integrations/sdk/ios/push/deep-links/).

## Capture push metrics with `UserNotifications`[](#metrics-userNotifications)

If you’re using a version of iOS that supports `UserNotifications`, you can track metrics using our `UNNotificationContent` helper.

```swift
func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse,
    withCompletionHandler completionHandler: @escaping () -> Void
) {
    // This 1 line of code might be all that you need!
    MessagingPush.shared.userNotificationCenter(center, didReceive: response, withCompletionHandler: completionHandler)

    // If you use `UserNotifications` for more then Customer.io push notifications, you can check 
    // if the SDK handled the push for you or not. 
    let handled = MessagingPush.shared.userNotificationCenter(center, didReceive: response, withCompletionHandler: completionHandler)
    if !handled {
        // Notification was *not* displayed by Customer.io. Handle the notification yourself. 
    }
}
```

## Extract delivery ID and token[](#metrics-direct)

If you’re not using a version of iOS that supports `UserNotifications`, you should send the push metric manually by extracting the `CIO-Delivery-ID` and `CIO-Delivery-Token` parameters directly to track push metrics.

```swift
guard let deliveryID: String = notificationContent.userInfo["CIO-Delivery-ID"] as? String, 
      let deviceToken: String = notificationContent.userInfo["CIO-Delivery-Token"] as? String else {
    // Not a push notification delivered by Customer.io
    return
}

MessagingPush.shared.trackMetric(deliveryID: deliveryID, event: .delivered, deviceToken: deviceToken)
```

## Disable automatic push tracking[](#disable-automatic-push-tracking)

Automatic push metric recording is enabled by default when you install the SDK. You can disable this behavior in the SDK’s configuration.

```swift
MessagingPushAPN.initialize(
    withConfig: MessagingPushConfigBuilder()
        .autoTrackPushEvents(false) // Disable automatic push tracking
        .build()
)
```

---

## Push > Push setup

**Source:** /integrations/sdk/ios/push/push-setup

# Set up push notifications

Our iOS SDK supports push notifications over APN or FCM. This page can help you get started with either service.

## Before you begin[](#before-you-begin)

This page explains how to register for push notifications using our SDK. But, before you can send push notifications, you need to add your push service credentials to Customer.io. See our [push service certificates](/integrations/sdk/ios/push/push-certificates) to learn more.

If you haven’t already, you’ll need to install the push package for the push service you use—APNs or FCM—and enable the *Push Notifications* capability [in XCode](https://developer.apple.com/documentation/xcode/adding-capabilities-to-your-app). For FCM users, you’ll also need to install the `CioFirebaseWrapper` dependency. See our [quick start guide](/integrations/sdk/ios/quick-start-guide) page for installation instructions.

## Register for push notifications[](#push-with-apns)

The instructions in this section set you up to receive simple push notifications with a `body` and `title`. After you follow these instructions, you’ll need to do a bit more work to support [rich push notifications](/integrations/sdk/ios/push/rich-push).

The SDK automatically handles push registration and push clicks for you. However, you’ll still need to identify users before you can send them push notifications.

### 1\. Initialize the push service[](#1-initialize-the-push-service)

After you initialize the SDK, initialize the push service that you use in your app. Your code changes slightly depending on the push service you use.

 APNs

#### APNs[](#APNs)

```swift
import CioDataPipelines
import CioMessagingPushAPN
import UIKit

@main
// Add the CioAppDelegateWrapper to handle push notifications and device token registration
class AppDelegateWithCioIntegration: CioAppDelegateWrapper<AppDelegate> {}

class AppDelegate: UIResponder, UIApplicationDelegate {
 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
     var cdpApiKey = YOUR_CDP_API_KEY
     var siteId = YOUR_SITE_ID
     
     let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
         .autoTrackDeviceAttributes(true)
         .autoTrackUIKitScreenViews()
         .migrationSiteId(siteId)

     CustomerIO.initialize(withConfig: config.build())

     // Initialize messaging features after initializing Customer.io SDK        
     MessagingPushAPN.initialize(
         withConfig: MessagingPushConfigBuilder()
             // optionally, configure the push module by calling functions on the builder. Such as: 
             .autoFetchDeviceToken(true)
             // See section below to find all the configuration options you can set. 
             .build()
     )

     return true
 }
} 
```

 FCM

#### FCM[](#FCM)

```swift
import CioDataPipelines
import CioFirebaseWrapper
import FirebaseCore
import FirebaseMessaging
import Foundation
import UIKit

@main
// Add the CioAppDelegateWrapper to handle push notifications and device token registration
class AppDelegateWithCioIntegration: CioAppDelegateWrapper<AppDelegate> {}

class AppDelegate: NSObject, UIApplicationDelegate {
 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
     // FCM provides a device token to the app that
     // you send to the Customer.io SDK.

     // Initialize the Firebase SDK.
     FirebaseApp.configure()

     let siteId = YOUR_SITE_ID
     let cdpApiKey = YOUR_CDP_API_KEY

     // Configure and initialize the Customer.io SDK
     let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
         .migrationSiteId(siteId)
         .autoTrackUIKitScreenViews()
         .autoTrackDeviceAttributes(true)
  
     CustomerIO.initialize(withConfig: config.build())

     // Initialize messaging features after initializing Customer.io SDK        
     MessagingPushFCM.initialize(
         withConfig: MessagingPushConfigBuilder()
             // optionally, configure the push module by calling functions on the builder. Such as: 
             .autoFetchDeviceToken(true)
             // See section below to find all the configuration options you can set. 
             .build()
     )

     return true
 }
}
```

### 2\. Identify your audience[](#2-identify-your-audience)

[Identify the person](/integrations/sdk/ios/tracking/identify) if you have not already. Even after you add a device token, you can’t use it until you associate it with a person.

```swift
CustomerIO.shared.identify(userId: "989388339", traits: ["first_name": firstName]) 
```

When you identify a person, you should see their device token in your workspace. You can send a simple push notification to test your implementation.

Note that when you [identify a different person](/integrations/sdk/ios/tracking/identify/) or [stop identifying a person](/integrations/sdk/ios/tracking/identify/#clearIdentify), the SDK automatically removes the device token from any previously identified profile. This ensures that a device token is only registered to the *currently identified profile* in the SDK and prevents you from sending duplicate messages messaging the wrong person.

## Push configuration options[](#push-configuration-options)

When you initialize your preferred push package (`CioMessagingPushAPN` or `CioMessagingPushFCM`), you can pass configuration options determining how the push package functions.

Option

Type

Default

Description

`autoFetchDeviceToken`

boolean

`true`

When `true`, the package automatically fetches the device token for push notifications.

`autoTrackPushEvents`

boolean

`true`

Automatically track `opened` and `delivered` metrics based on push notifications.

`showPushAppInForeground`

boolean

`true`

Show push notifications when the app is in the foreground. Used only if customer’s AppDelegate doesn’t implement `UNUserNotificationCenterDelegate`.

`appGroupId`

String

`nil`

Sets the App Group identifier for reliable push delivery tracking. When set, the SDK stores undelivered metrics in shared storage so they can be recovered on the next app launch. See [App Groups for push tracking](/integrations/sdk/ios/push/app-groups/) for setup instructions.

## Next steps[](#next-steps)

To ensure reliable push delivery tracking, [set up rich push](/integrations/sdk/ios/push/rich-push/). Rich push adds a Notification Service Extension (NSE) to your app, which the SDK uses to track delivery metrics. Without the NSE, the SDK can’t confirm that a push notification was delivered to the device.

After you set up rich push, you can further improve delivery tracking with [App Groups](/integrations/sdk/ios/push/app-groups/).

---

## Push > Rich push

**Source:** /integrations/sdk/ios/push/rich-push

# Set up rich push

Set up your app to support push notifications with images and deep links.

## 1\. Add a service extension to your project[](#1-add-a-service-extension-to-your-project)

[Add a Service App Extension to your project in Xcode](https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications).

You should now see a new file added to your Xcode project. The file is probably named `NotificationService` and looks similar to this.

```swift
import UserNotifications
   
class NotificationService: UNNotificationServiceExtension {
   
    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
   
    }
   
    override func serviceExtensionTimeWillExpire() {
   
    }
}
```

## 2\. Update the service extension[](#2-update-the-service-extension)

 Xcode 16.3+ users

If you encounter errors like “not available due to missing import of defining module ‘CioMessagingPush’”, go to your NotificationServiceExtension target’s **Build Settings** and set **Member Import Visibility** (`SWIFT_MEMBER_IMPORT_VISIBILITY`) to “Package” or disable it.

Modify your new `NotificationService` extension by selecting the push package you want to import and calling the appropriate Customer.io functions. Your code changes if:

*   Customer.io is your only push/rich push provider
*   Customer.io **is not** your only provider
*   You want to take advantage of push features outside the Customer.io, like action buttons; in this case, you’ll need to set your own completion handler.

 App Groups for delivery tracking

The code samples below include a commented-out `.appGroupId()` line. To improve push delivery metric reliability, [set up App Groups](/integrations/sdk/ios/push/app-groups/) and enable that line with your App Group identifier.

 Customer.io push only

#### Customer.io push only[](#Customer.io push only)

```swift
// Keep the import for your push provider—FCM or APN, and
// remove the other import statement
import CioMessagingPushAPN 
import CioMessagingPushFCM 
import UserNotifications

class NotificationService: UNNotificationServiceExtension {
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
       // Use `MessagingPushFCM` if you are using FCM as push service provider
       MessagingPushAPN.initializeForExtension(
            withConfig: MessagingPushConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY")
                // Optional: specify region where your Customer.io account is located (.US or .EU). Default: US
                .region(.US)
                // Optional: set App Group ID for reliable push delivery tracking
                // .appGroupId("group.com.example.myapp.cio")
                .build()
       )

       MessagingPush.shared.didReceive(request, withContentHandler: contentHandler)
    }

    override func serviceExtensionTimeWillExpire() {
        MessagingPush.shared.serviceExtensionTimeWillExpire()
    }
}
```

 Multiple push services

#### Multiple push services[](#Multiple push services)

```swift
// Keep the import for your push provider—FCM or APN, and
// remove the other import statement
import CioMessagingPushFCM
import CioMessagingPushAPN
import UserNotifications
   
class NotificationService: UNNotificationServiceExtension {
   
    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        // Due to the behavior of Notification Service Extensions in iOS, you need to 
        // initialize the Push Module in both your host app and in your Notification Service.         
        // The config builder also lets you you to configure the push module.
       // Use `MessagingPushFCM` if you use FCM as your push service provider
       MessagingPushAPN.initializeForExtension(
            withConfig: MessagingPushConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY")
                .autoTrackPushEvents(true)
                // Optional: specify region where your Customer.io account is located (.US or .EU). Default: US
                .region(.US)
                // Optional: set App Group ID for reliable push delivery tracking
                // .appGroupId("group.com.example.myapp.cio")
                .build()
       )

        // If you use a service other than Customer.io to send rich push,
        // you can check if the SDK handled the rich push for you. If it did not, you
        // know that the push was *not* sent by Customer.io and you can try another way.
        let handled = MessagingPush.shared.didReceive(request, withContentHandler: contentHandler)
        if !handled {
            // Rich push was *not* sent by Customer.io. Handle the rich push in another way.
        }
    }
   
    override func serviceExtensionTimeWillExpire() {
        MessagingPush.shared.serviceExtensionTimeWillExpire()
    }
}
```

 Custom completion handler

#### Custom completion handler[](#Custom completion handler)

```swift
// Keep the import for your push provider—FCM or APN, and
// remove the other import statement
import CioMessagingPushFCM
import CioMessagingPushAPN
import UserNotifications
import CioDataPipelines
   
class NotificationService: UNNotificationServiceExtension {
   
    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        // Due to the behavior of Notification Service Extensions in iOS, you need to 
        // initialize the Push Module in both your host app and in your Notification Service.         
        // The config builder also lets you you to configure the push module.
        // Use `MessagingPushFCM` if you use FCM as your push service provider
       MessagingPushAPN.initializeForExtension(
            withConfig: MessagingPushConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY")
                .autoTrackPushEvents(true)
                // Optional: specify region where your Customer.io account is located (.US or .EU). Default: US
                .region(.US)
                // Optional: set App Group ID for reliable push delivery tracking
                // .appGroupId("group.com.example.myapp.cio")
                .build()
       )

        // If you need to add features, like showing action buttons in your push, 
        // you can set your own completion handler.
        MessagingPush.shared.didReceive(request) { notificationContent in
            if let mutableContent = notificationContent.mutableCopy() as? UNMutableNotificationContent {
                // Modify the push notification like adding action buttons!
            }
            contentHandler(notificationContent)
        }
    }
   
    override func serviceExtensionTimeWillExpire() {
        MessagingPush.shared.serviceExtensionTimeWillExpire()
    }
}
```

Your app can now display rich push notifications in your app, including images, etc. See [Deep Links](/integrations/sdk/ios/push/deep-links) to enable deep links in your push notifications.

## Improve delivery metrics with App Groups[](#improve-delivery-metrics-with-app-groups)

For the most reliable push delivery tracking, set up [App Groups for push tracking](/integrations/sdk/ios/push/app-groups/). App Groups let the SDK recover delivery metrics that iOS might otherwise discard when it terminates the Notification Service Extension. Without this setup, you may lose some delivery data.

---

## Push > Sound in push

**Source:** /integrations/sdk/ios/push/sound-in-push

# Sound in push notifications

When you send a notification, it can play an alert on your audience’s device. The sound file must be bundled in your app, and you’ll specify which sound you want to play in your notification payload.

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 > Test push

**Source:** /integrations/sdk/ios/push/test-push

# Test your push implementation

After you set up push notifications, you should send some test messages. You can send messages through the Customer.io push composer. If your app is set up to send more than the standard title, body, image, and link, you’ll need to send a [custom payload](/journeys/push-custom-payloads/#getting-started-with-custom-payloads).

[![The custom payload editor for a push notification](https://customer.io/images/push-custom-ios.png)](#0c9b0c5b97db4dbadfe8437d8b8fe49c-lightbox)

The payloads below represent what your app can expect to receive from Customer.io. If you use a custom payload, you’ll need to use the format(s) below to make sure that the SDK receives your message properly.

When testing, you should:

*   Set the `link` to the deep link URL that you want to open when your tester taps your notification.
*   Set the `image` to the URL of an image you want to show in your notification. It’s important that the image URL starts with `https://` and *not* `http://` or the image might not show up.
    
     APNS payload
    
    #### APNS payload[](#APNS payload)
    
    ```json
    {
        "aps": {
            // basic iOS message and options go here
            "mutable-content": 1,
            "sound": "default",
            "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.
            
    
     FCM payload
    
    #### FCM payload[](#FCM payload)
    
    ```json
    {
      "message": {
        "apns": {
          "payload": {
            "aps": {
              // basic iOS message and options go here
              "mutable-content": 1,
              "sound": "default",
              "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.
                        
                *   *Custom key-value pairs\** any type
                    
                    Additional properties that you've set up your app to interpret outside of the Customer.io SDK.

---

## Tracking > Anonymous activity

**Source:** /integrations/sdk/ios/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, and we merge anonymous activity with the identified person, the 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 with their identified profile, and their previously-anonymous `screen` view triggers the campaign you set up for people who visited the product page.

You can return a person’s anonymous ID at ay time by calling `CustomerIO.shared.anonymousId`.

---

## Tracking > Identify

**Source:** /integrations/sdk/ios/tracking/identify

# Identify people

You need to identify a person using a mobile device before you can send them messages or track events for things they do in your app. You need the **CioDataPipelines** package to identify people.

## 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.  If you already registered a device token, identifying a person automatically associates the token with the identified person. You can register for a device token before or after you identify a person. See our [Push Documentation](/integrations/sdk/ios/push/push-setup/) for help registering device tokens.

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 the following parameters:

*   **userId** (required): The unique value representing a person—an ID or email address that represents a person in Customer.io (and your downstream destinations).
*   **traits** (Optional): Contains [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 set for a person. [https://customer.io/api/track/#operation/identify](https://customer.io/api/track/#operation/identify)

```swift
import CioDataPipelines

CustomerIO.shared.identify(userId: "989388339", traits: ["first_name": firstName]) 

// `traits` accepts [String: Any] or an `Encodable` object 

// 1. [String: Any]:
let traits = ["first_name": "Dana", "last_name": "Green"]
CustomerIO.shared.identify(userId: "989388339", traits: traits)


// 2. `Encodable` object:
struct IdentifyRequestTraits: Encodable {
  let firstName: String
  let lastName: String 
}
CustomerIO.shared.identify(userId: "989388339", traits: IdentifyRequestTraits(firstName: "Dana", lastName: "Green"))
```

### Update a person’s attributes[](#identify-again)

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 `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 `profileAttributes`.

```swift
CustomerIO.shared.profileAttributes = ["favorite_food": "pizza"]
```

You only need to pass the attributes that you want to create or modify to `profileAttributes`. For example, if you identify a new person with the attribute `["first_name": "Dana"]`, and then you call `CustomerIO.shared.profileAttributes = ["favorite_food": "pizza"]` after that, the person’s `first_name` attribute will still be `Dana`.

### 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.
    

#### Custom device attributes[](#custom-device-attributes)

When we collect device attributes, you can also set custom device attributes with the `deviceAttributes` method. You might do this to save app preferences, time zone, or other custom values specific to the device.

```swift
CustomerIO.shared.deviceAttributes = ["company" : "cio", "checklist" : "complete"]
```

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](#identify-again) rather than the device.

#### Disable automatic device attribute collection[](#disable-attributes)

By default, the SDK automatically collects the device attributes [defined above](#device-attributes). You can change your config to prevent the SDK from automatically collecting these attributes.

```swift
import CioDataPipelines

let config = SDKConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY")
            .migrationSiteId("YOUR_SITE_ID")
            .autoTrackDeviceAttributes(false)

CustomerIO.initialize(withConfig: config.build()) 
```

## 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).

```swift
// Future calls to the Customer.io SDK are anonymous 
CustomerIO.shared.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.

---

## Tracking > Lifecycle events

**Source:** /integrations/sdk/ios/tracking/lifecycle-events

# Mobile Lifecycle events

By default, the Customer.io SDK for iOS automatically tracks lifecycle events for your users. These are 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` call. 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": "app.installer@example.com",
  "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.

```swift
import CioDataPipelines

CustomerIO.shared.track(
    name: "Application Crashed", 
    properties: [
      "url": "urls://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 to the SDK’s config builder.

```swift
import CioDataPipelines

let config = SDKConfigBuilder(cdpApiKey: "CDP_API_KEY")
  .trackApplicationLifecycleEvents(false)
  .build()

CustomerIO.initialize(withConfig: config)
```

---

## Tracking > Location

**Source:** /integrations/sdk/ios/tracking/location

# Location tracking

## How it works[](#how-it-works)

The Location module captures location (with user consent) from your app and attaches it to a person’s profile in Customer.io. You can use this data for geo-aware messaging and audience segmentation with more accuracy than [IP-based geolocation](/journeys/geolocation/).

When you identify a person, the SDK includes the latest location in the identify call. The SDK also sends a `Location Update` event to the person’s activity timeline, which you can use in journeys and segments. To balance location updates with battery and data usage, the SDK limits location updates once a day (at most)—and only sends that update when the person has moved more than 1 kilometer since the last update.

The SDK does not request location permission on its own—your app must handle the permission flow.

## Install the location module[](#install)

Add the `CioLocation` package to your project. If you use Swift Package Manager, add it the same way you added the other Customer.io packages. The `CioLocation` library is part of the [`customerio-ios`](https://github.com/customerio/customerio-ios) package.

If you use CocoaPods, add the pod to your Podfile:

```ruby
pod 'CustomerIO/Location'
```

## Initialize the SDK with the location module[](#initialize-the-sdk-with-the-location-module)

Register `LocationModule` when you initialize the SDK. The module takes a `LocationConfig` where you set the tracking mode.

Option

Type

Default

Description

`mode`

`LocationTrackingMode`

`.manual`

Controls how and when the SDK captures location. See [tracking modes](#tracking-modes) below.

### Tracking modes[](#tracking-modes)

Mode

Description

`.manual`

Your app controls when it captures location. Call `setLastKnownLocation(_:)` or `requestLocationUpdate()` to provide location. You should use this option when your app already has a location-tracking mechanism or you want full control over when you capture location data.

`.onAppStart`

The SDK automatically captures a one-shot location once per app launch when your app enters the foreground. You can still call `setLastKnownLocation(_:)` or `requestLocationUpdate()` alongside automatic capture. Use this for hands-off location tracking with minimal battery impact.

`.off`

Disables location tracking entirely. All location calls become silent and location is not included in identify calls. Use this if you want to register the module but disable it at runtime.

```swift
import CioDataPipelines
import CioLocation

let config = SDKConfigBuilder(cdpApiKey: "your-cdp-api-key")
    .addModule(LocationModule(config: LocationConfig(mode: .manual)))
    .build()

CustomerIO.initialize(withConfig: config)
```

## Location APIs[](#location-apis)

The module provides two methods to capture location. You can call either method as often as you like; the SDK always caches the latest coordinates for profile enrichment, but sends a `Location Update` event no more than once a day—and only if the person has moved more than 1 kilometer since the last update. No matter how frequently you call these methods, the SDK throttles the updates for you so as not to overwhelm your workspace with profile updates.

### `setLastKnownLocation`[](#setlastknownlocation)

Pass a `CLLocation` directly from your app’s own location system. This doesn’t require any location permissions from the SDK. Your app manages location access independently of Customer.io.

```swift
import CoreLocation
import CioLocation

// From a CLLocationManager delegate callback
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    if let location = locations.last {
        CustomerIO.location.setLastKnownLocation(location)
    }
}
```

### `requestLocationUpdate`[](#requestlocationupdate)

Request a one-shot location from the SDK using Core Location. Use this if your app doesn’t have its own location system. Your app must request location permission before calling this method—the SDK won’t prompt the user.

If a user doesn’t grant permission or location services are disabled, the request is ignored—no crash or exception. If a request is already in progress, additional calls are ignored until the current request completes.

Add the required key to your `Info.plist`:

```xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>We use your location to personalize your experience.</string>
```

Request permission at runtime, then call the SDK:

```swift
import CoreLocation
import CioLocation

let locationManager = CLLocationManager()

// Request permission first
locationManager.requestWhenInUseAuthorization()

// After permission is granted
CustomerIO.location.requestLocationUpdate()
```

## Profile switch behavior[](#profile-switch-behavior)

When you call `CustomerIO.shared.clearIdentify()`, the SDK clears cached location data so that one person’s location doesn’t carry over to another person’s profile. The next person you identify starts with a clean slate.

Location persists across app restarts. When your app relaunches, the SDK restores the cached location so that the next `identify()` call includes it automatically.

---

## Tracking > Screen events

**Source:** /integrations/sdk/ios/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 `title` 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 they use. Screen events also update your audience’s “Last Visited” attribute, which can help you track how recently people used your app.

## Automatic screen tracking[](#auto-screenview)

We can automatically track screens for UIKit-based apps with the `.autoTrackUIKitScreenViews` configuration option. When you enable automatic screen tracking, the SDK sends a `screen` call every time a person visits a screen in your app. **For apps not using UIKit, like SwiftUI apps, you should [manually track screen events](#manual-screenview).**

To enable automatic screen tracking, you can pass `.autoTrackUIKitScreenViews()` to the `SDKConfigBuilder` or you can [customize the behavior for automatic screen tracking](#customize-automatic-screen-tracking) if you want to filter the screens the SDK sends events for or add additional properties to the `screen` events.

When you’re done, we recommend you [test that automatic screen tracking works for your app](#test-automatic-screen-tracking). If you encounter issues, you can always send screen events manually.

```swift
import CioDataPipelines

let config = SDKConfigBuilder(cdpApiKey: "CDP_API_KEY")
  .autoTrackUIKitScreenViews()
  .build()
```

If you don’t use UIKit, or otherwise need to send your own screen events, you can [send screen events manually](#manual-screenview).

### 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!

```swift
import CioDataPipelines

let config = SDKConfigBuilder(cdpApiKey: "CDP_API_KEY")
  .autoTrackUIKitScreenViews()
  .screenViewUse(screenView: .all)
  .build()
```

### Screen names[](#screen-names)

When you enable automatic screen views, the SDK automatically names the screen as the class name of the `UIViewController`, minus `ViewController`. For example, if you have a class `EditProfileViewController` in your code base, the SDK will automatically send a screenview event with the screen name `EditProfile`.

### Customize automatic screen tracking[](#customize-automatic-screen-tracking)

You can also set additional parameters to customize the behavior of automatic screen tracking.

*   `autoScreenViewBody`: (optional) Closure that returns a dictionary of properties that you want to send with each automatic `screen` call.
*   `filterAutoScreenViewEvents`: (optional) Closure that returns a boolean—`true` to send an automatic `screen` call; `false` to prevent an automatic call.

```swift
import CioDataPipelines

let config = SDKConfigBuilder(cdpApiKey: "CDP_API_KEY")
  .autoTrackUIKitScreenViews(
    autoScreenViewBody: {
      return [:]
    },  // optional
    filterAutoScreenViewEvents: { (viewController: UIViewController) in
      return true
    }  // optional
  )
  .build()

CustomerIO.initialize(withConfig: config)
```

### Test automatic screen tracking for your app[](#test-automatic-screen-tracking)

The automatic screen tracking feature is designed to work with most UIKit-based apps, but it may not work with especially complex navigation structures. If you’ve got a significantly complex UIKit-based navigation structure, you may need to send screen events manually.

When you enable automatic screen tracking, you may want to enable info-level logging and walk through your app to make sure screen events come through as expected. If you encounter issues, you probably need to [send events manually for problematic screens](#manual-screenview).

```swift
import CioDataPipelines

let config = SDKConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY")
            .migrationSiteId("YOUR_SITE_ID")
            .autoTrackUIKitScreenViews(true)
            .logLevel("info")

CustomerIO.initialize(withConfig: config.build()) 
```

## Manually track screen events[](#manual-screenview)

Screen events use the `.screen` method. Like other event types, you can add a `properties` object containing additional information about the event or the currently-identified person.

```swift
import CioDataPipelines

// You can send an event with or without `properties`.
CustomerIO.shared.screen(title: "DailyBaseballScores")
// `properties` accepts [String: Any] or an `Encodable` object 
// 1. [String: Any]:
let data = ["prev_screen": "homescreen", "seconds_in_app": "120"]
CustomerIO.shared.screen(title: "DailyBaseballScores", properties: data)

// 2. A custom `Encodable` type:
struct Screen: Encodable {
  let prevScreen: String
  let secondsInApp: Int
}
CustomerIO.shared.screen(title: "DailyBaseballScores", properties: Screen(prevScreen: "homescreen", secondsInApp: 120))
```

---

## Tracking > Track events

**Source:** /integrations/sdk/ios/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.<attribute>}}`.

```swift
import CioDataPipelines

CustomerIO.shared.track(name: "logged_in", properties: ["ip": "127.0.0.1"])

// You don't need to send `data`
CustomerIO.shared.track(name: "played_game")

// `data` accepts [String: Any] or an `Encodable` object 
// 1. [String: Any]:
let data = ["product": "socks", "price": "23.45"]
CustomerIO.shared.track(name: "purchase", properties: data)

// 2. A custom `Encodable` type:
struct Purchase: Encodable {
  let product: String
  let price: Double
}
CustomerIO.shared.track(name: "purchase", properties: Purchase(product: "socks", price: 23.45))
```

 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.

```swift
CustomerIO.shared.track(name: "User Deleted)
```

---

## Whats new > 2.x upgrade

**Source:** /integrations/sdk/ios/whats-new/2.x-upgrade

# Upgrade from 1x to 2x

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 1.x to 2.x[](#upgrade-from-1x-to-2x)

### Singleton API is now enforced[](#singleton-api-is-now-enforced)

In version 1.x of the Customer.io iOS SDK, could use the SDK in 2 ways:

1.  Singleton API:

```swift
CustomerIO.initialize(...) // initialize the singleton SDK instance
Customer.shared.track(...) // use the singleton SDK instance 
```

2.  Non-singleton API:

```swift
let cio = CustomerIO(...) // initialize the non-singleton SDK instance
cio.track(...)            // use the non-singleton SDK instance 
```

In version 2.x, we removed the non-singleton API. To successfully migrate, you need to replace any code using a non-singleton with the singleton instance:

```swift
// Replace the non-singleton instance:
cio.track(...) 
messagingPush.application(...)
// With `CustomerIO.shared` or `MessagingPush.shared`:
CustomerIO.shared.track(...) 
MessagingPush.shared.application(...)
```

If your app uses a technique like dependency injection, you can keep your code base as-is and simply replace code where you create new instances of the SDK:

```swift
// For example, if you have code that accepts a CustomerIO dependency in the constructor (to easily allow mocking the Customer.io SDK):
class Repository {
    let customerIO: CustomerIO

    init(customerIO: CustomerIO) {
        self.customerIO = customerIO
    }
    
    func acceptFriendRequest() {
        ...
        self.customerIO.track(...)
        ...
    }
}

// You can keep your Repository as-is, but you need to change where you create instances from:
let repository = Repository(customerIO: CustomerIO(...))
repository.acceptFriendRequest()
// To:
let repository = Repository(customerIO: CustomerIO.shared) // Don't forget to initialize the SDK 😉
repository.acceptFriendRequest()
```

### Configuration of the SDK happens during initialization[](#configuration-of-the-sdk-happens-during-initialization)

In version 1.x of the Customer.io iOS SDK, you configured the SDK through a `.config` function:

```swift
CustomerIO.config {
  $0.autoTrackScreenViews = true
}
```

In version 2.x of the Customer.io iOS SDK, we moved the `.config` function into `CustomerIO.initialize`. You’ll need to move your configuration into the SDK initialization process to migrate:

```swift
CustomerIO.initialize(siteId: "YOUR SITE ID", apiKey: "YOUR API KEY", region: Region.EU) { config in
  config.autoTrackScreenViews = true
}
```

Visit [the getting started doc](/integrations/sdk/ios/getting-started/) to learn more about SDK configuration.

### SDK initialization: required parameters[](#sdk-initialization-required-parameters)

In version 1.x of the Customer.io SDK, the function `CustomerIO.initialize` contained optional parameters. We had to remove those and make all parameters required.

To migrate to 2.x of the SDK, fill in the rest of the parameters in your `initialize` function:

```swift
// v1.x
CustomerIO.initialize(siteId: "YOUR SITE ID", apiKey: "YOUR API KEY")

// v2.x
CustomerIO.initialize(siteId: "YOUR SITE ID", apiKey: "YOUR API KEY", region: Region.US) {}
// Optionally, if you want to configure settings of the SDK, do so in initialization. 
CustomerIO.initialize(siteId: "YOUR SITE ID", apiKey: "YOUR API KEY", region: Region.EU) { config in
  config.autoTrackScreenViews = true
}
```

Visit [the getting started doc](/integrations/sdk/ios/getting-started/) to learn more about SDK configuration.

### Rich push initialization[](#rich-push-initialization)

If you have [followed our docs to setup rich push](/integrations/sdk/ios/rich-push/) in your app, you should have a `Notification Service Extension` file in your code base.

Because of the behavior of Notification Service Extensions in iOS, you need to initialize the Customer.io SDK in your host app *and in your Notification Service*.

```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/ios/rich-push/) to learn more about rich push setup, SDK initialization, and SDK configuration.

### Cocoapods users must manually install Firebase dependencies[](#cocoapods-users-must-manually-install-firebase-dependencies)

We removed all Firebase SDKs as dependencies from the `CustomerIO/MessagingPushFCM` Cocoapod. This means that you need to install the Firebase Cloud Messaging (FCM) dependencies in your `Podfile` on your own.

If you installed the Customer.io SDK using Swift Package Manager, this change does not effect you.

### We fixed a bug with custom attributes that may impact your data[](#we-fixed-a-bug-with-custom-attributes-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 v1 that converted keys in your custom data to `snake_case`. This bug is fixed in v2 of the iOS 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 our Android SDK.*.

```swift
// 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)

```swift
// Anywhere you call the Customer.io SDK and provide custom attributes like this: 
CustomerIO.shared.identify("dana@example.com", data: ["firstName": "Dana"])

// Consider sending duplicate data with snake_case
CustomerIO.shared.identify("dana@example.com", data: [
    "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:

```swift
CustomerIO.shared.identify("dana@example.com", data: [
    "firstName": "Dana", // We can remove the snake_case attribute and go back to just camelCase! 
])
```

---

## Whats new > 3.13.0 upgrade

**Source:** /integrations/sdk/ios/whats-new/3.13.0-upgrade

# Upgrade from 3.x to 3.13.0

This page introduces a new `CioAppDelegateWrapper` pattern for iOS that simplifies push notification setup and eliminates the need for method swizzling.

## What changed?[](#what-changed)

The changes are mainly to align our SDK APIs across different platforms. No functional changes to be expected.

## Upgrade process[](#upgrade-process)

### Attributes[](#attributes)

*   `profileAttributes` property is deprecated
    *   Getter has no replacement, the mobile SDK doesn’t expose the user’s profile attributes
    *   Setter is replaced with `setProfileAttributes(attributes: [String: Any])`
*   `deviceAttributes` property is deprecated
    *   Getter has no replacement, the mobile SDK doesn’t expose the user’s device attributes
    *   Setter is replaced with `setDeviceAttributes(attributes: [String: Any])`

### Tracking[](#tracking)

*   Identifying a user
    
    *   These variants of `identify` are deprecated:
        *   `identify<T: Codable>(traits: T)` where traits are a generic type
        *   `identify<RequestBody: Codable>(userId: String, traits: RequestBody?)`
    *   You should use this instead:
        *   `identify(userId: String, traits: [String: Any]?)`
        *   `setProfileAttributes(attributes: [String: Any])` can now be used to track anonymous profile attributes
*   Tracking an event
    
    *   These variants of `track` are deprecated:
        *   `track<RequestBody: Codable>(name: String, properties: RequestBody?)` where traits are a generic type
    *   You should use this instead:
        *   `track(name: String, properties: [String: Any]?)`
*   Screen tracking
    
    *   These variants of `screen` are deprecated:
        *   `screen<RequestBody: Codable>(title: String, properties: RequestBody?)` where traits are a generic type
    *   You should use this instead:
        *   `screen(title: String, properties: [String: Any]?`

---

## Whats new > 3.9.0 upgrade

**Source:** /integrations/sdk/ios/whats-new/3.9.0-upgrade

# Upgrade from 3.x to 3.9.0

This page 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 3.9.0 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 APNs[](#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 (3.x)

#### Before (3.x)[](#Before \(3.x\))

```swift
import UIKit
import CioMessagingPushAPN

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        // Initialize the Customer.io SDK 
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // If your account is in the EU region, uncomment the next line
            // .region(.EU)
            .migrationSiteId(siteId) // only required if you used version 2.x or earlier
            .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
            .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
        CustomerIO.initialize(withConfig: config.build())

        return true
    }
}
```

 After (3.9.0)

#### After (3.9.0)[](#After \(3.9.0\))

```swift
import UIKit
import CioMessagingPushAPN

@main
class AppDelegateWithCioIntegration: CioAppDelegateWrapper<AppDelegate> {}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        // Initialize the Customer.io SDK 
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // If your account is in the EU region, uncomment the next line
            // .region(.EU)
            .migrationSiteId(siteId) // only required if you used version 2.x or earlier
            .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
            .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
        CustomerIO.initialize(withConfig: config.build())

        return true
    }
}
```

### 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 (3.x)

#### Before (3.x)[](#Before \(3.x\))

```swift
import SwiftUI
import CioMessagingPushAPN

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Initialize the Customer.io SDK 
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // If your account is in the EU region, uncomment the next line
            // .region(.EU)
            .migrationSiteId(siteId) // only required for migration
            .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
            .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
        CustomerIO.initialize(withConfig: config.build())

        return true
    }
}
```

 After (3.9.0)

#### After (3.9.0)[](#After \(3.9.0\))

```swift
import SwiftUI
import CioMessagingPushAPN

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(CioAppDelegateWrapper<AppDelegate>.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Initialize the Customer.io SDK 
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // If your account is in the EU region, uncomment the next line
            // .region(.EU)
            .migrationSiteId(siteId) // only required for migration
            .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
            .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
        CustomerIO.initialize(withConfig: config.build())

        return true
    }
}
```

## Update with FCM[](#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 (3.x)

#### Before (3.x)[](#Before \(3.x\))

```swift
import UIKit
import CioMessagingPushFCM
import Firebase
import FirebaseMessaging

@main
class AppDelegate: NSObject, UIApplicationDelegate {
 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
     // Initialize the Firebase SDK.
     FirebaseApp.configure()

     let siteId = YOUR_SITE_ID
     let cdpApiKey = YOUR_CDP_API_KEY

     // Configure and initialize the Customer.io SDK
     let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
         .migrationSiteId(siteId)
         .autoTrackUIKitScreenViews()
         .autoTrackDeviceAttributes(true)
  
     CustomerIO.initialize(withConfig: config.build())

     // Initialize messaging features after initializing Customer.io SDK        
     MessagingPushFCM.initialize(
         withConfig: MessagingPushConfigBuilder()
             // optionally, configure the push module by calling functions on the builder. Such as: 
             .autoFetchDeviceToken(true)
             // See section below to find all the configuration options you can set. 
             .build()
     )

     return true
 }
}
```

 After (3.9.0)

#### After (3.9.0)[](#After \(3.9.0\))

```swift
import UIKit
import CioMessagingPushFCM
import Firebase
import FirebaseMessaging

@main
class AppDelegateWithCioIntegration: CioAppDelegateWrapper<AppDelegate> {}

class AppDelegate: NSObject, UIApplicationDelegate {
 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
     // Initialize the Firebase SDK.
     FirebaseApp.configure()

     let siteId = YOUR_SITE_ID
     let cdpApiKey = YOUR_CDP_API_KEY

     // Configure and initialize the Customer.io SDK
     let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
         .migrationSiteId(siteId)
         .autoTrackUIKitScreenViews()
         .autoTrackDeviceAttributes(true)
  
     CustomerIO.initialize(withConfig: config.build())

     // Initialize messaging features after initializing Customer.io SDK        
     MessagingPushFCM.initialize(
         withConfig: MessagingPushConfigBuilder()
             // optionally, configure the push module by calling functions on the builder. Such as: 
             .autoFetchDeviceToken(true)
             // See section below to find all the configuration options you can set. 
             .build()
     )

     return true
 }
}
```

### 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 (3.x)

#### Before (3.x)[](#Before \(3.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: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Initialize the Customer.io SDK 
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // If your account is in the EU region, uncomment the next line
            // .region(.EU)
            .migrationSiteId(siteId) // only required for migration
            .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
            .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
        CustomerIO.initialize(withConfig: config.build())

        return true
    }
}
```

 After (3.9.0)

#### After (3.9.0)[](#After \(3.9.0\))

```swift
import SwiftUI
import CioMessagingPushFCM
import UserNotifications

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(CioAppDelegateWrapper<AppDelegate>.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Initialize the Customer.io SDK 
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // If your account is in the EU region, uncomment the next line
            // .region(.EU)
            .migrationSiteId(siteId) // only required for migration
            .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
            .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
        CustomerIO.initialize(withConfig: config.build())

        return true
    }
}
```

## Important Notes[](#important-notes)

1.  `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`
    *   `userNotificationCenter(_:willPresent:withCompletionHandler:)`
    *   `userNotificationCenter(_:didReceive:withCompletionHandler:)`
    *   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()`

---

## Whats new > 3.x upgrade

**Source:** /integrations/sdk/ios/whats-new/3.x-upgrade

# Upgrade from 2x to 3x

This page details breaking changes from the previous major version of the SDK, 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, the in-app messaging module, and the push module. The in-app and push modules are now required.

**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 **iOS**.
    
    [![set up your iOS source](https://customer.io/images/cdp-ios-source.png)](#67f0591daae0ccd9a402e4bfcb137c78-lightbox)
    
3.  Enter a *Name* for your integration, like “My iOS App”.
4.  We’ll present you with 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-ios-source-setup.png)](#be5c9653f8ae715bc7a9a87b2729f76c-lightbox)
    

Remember, you can also [connect your iOS app to services outside of Customer.io](/integrations/data-out/add-destination/)—like your analytics provider, data warehouse, or CRM.

### 2\. Import CioDataPipelines instead of CioTracking[](#2-import-ciodatapipelines-instead-of-ciotracking)

We’ve replaced the `CioTracking` package with `CioDataPipelines`. You’ll need to update your import statements to reflect this change.

If you see errors like *Missing required module ‘CioTracking’*, you can remove the package in XCode under *Frameworks and Libraries*.

```swift
// replace import CioTracking with:
import CioDataPipelines
```

### 3\. Update your `initialize` calls[](#3-update-your-initialize-calls)

You’ll initialize the new version of the SDK and its packages with `SDKConfigBuilder` objects instead of a `CustomerIOConfig`. A few of the configuration options changed. In particular,

*   `cdpApiKey` replaces `apiKey`: this is a new key that you got from [Step 1](#1-get-your-new-cdp-api-key)
*   `migrationSiteId` replaces `siteId`: this is the same key you used in the previous version of the SDK. **You need to include this property** to send remaining traffic when people update your app.
*   `autoTrackUIKitScreenViews` replaces `autoTrackScreenViews`: functionality is unchanged; we simply renamed the option to reflect support for UIKit and *not* SwiftUI.
*   **If you’re in our EU region**, make sure that you uncomment the `.region(.EU)` line in the sample below. Your config must include this property to send data to our EU data center.

 APNS

#### APNS[](#APNS)

```swift
import CioDataPipelines
import CioMessagingInApp
import CioMessagingPushAPN
import UIKit

@main
// Add the CioAppDelegateWrapper to handle push notifications and device token registration
class AppDelegateWithCioIntegration: CioAppDelegateWrapper<AppDelegate> {}

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        let cdpApiKey = YOUR_CDP_API_KEY
        let siteId = YOUR_SITE_ID
        
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // uncomment the line below if your account is in the EU region
            // .region(.EU)
            .autoTrackDeviceAttributes(true)
            .migrationSiteId(siteId)
            //replaces autoTrackScreenViews
            .autoTrackUIKitScreenViews()

        CustomerIO.initialize(withConfig: config.build())

        // Initialize messaging features after initializing Customer.io SDK
        MessagingInApp
            .initialize(withConfig: MessagingInAppConfigBuilder(siteId: siteId, region: .US).build())
            .setEventListener(self)
        MessagingPushAPN.initialize(
            withConfig: MessagingPushConfigBuilder()
                .autoFetchDeviceToken(true)    // Automatically fetch device token and upload to CustomerIO
                .autoTrackPushEvents(true)     // Automatically track push metrics
                .showPushAppInForeground(true) // Enable Notifications in the foreground
                .build()
        )
      
        UNUserNotificationCenter.current().delegate = self

        return true
    }   
```

 FCM

#### FCM[](#FCM)

```swift
import CioDataPipelines
import CioMessagingInApp
import CioMessagingPushFCM
import FirebaseCore
import FirebaseMessaging
import Foundation
import UIKit

@main
// Add the CioAppDelegateWrapper to handle push notifications and device token registration
class AppDelegateWithCioIntegration: CioAppDelegateWrapper<AppDelegate> {}

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
        // To set up FCM push: https://firebase.google.com/docs/cloud-messaging/ios/client
        // FCM provides a device token to the app that
        // you send to the Customer.io SDK.

        // Initialize the Firebase SDK.
        FirebaseApp.configure()

        let siteId = YOUR_JOURNEYS_SITE_ID
        let cdpApiKey = YOUR_CDP_API_KEY

        // Configure and initialize the Customer.io SDK
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // uncomment this line below if your account is in the EU region
            // .region(.EU)
            .migrationSiteId(siteId)
            .autoTrackDeviceAttributes(true)
            //replaces autoTrackScreenViews
            .autoTrackUIKitScreenViews()

  
        CustomerIO.initialize(withConfig: config.build())

        // Initialize messaging features after initializing Customer.io SDK
        MessagingInApp
            .initialize(withConfig: MessagingInAppConfigBuilder(siteId: siteId, region: .US).build())
            .setEventListener(self)
        MessagingPushFCM.initialize(
            withConfig: MessagingPushConfigBuilder()
                .autoFetchDeviceToken(true)    // Automatically fetch device token and upload to CustomerIO
                .autoTrackPushEvents(true)     // Automatically track push metrics
                .showPushAppInForeground(true) // Enable Notifications in the foreground
                .build()
        )

        // Manually get FCM device token. Then, we will forward to the Customer.io SDK.
        Messaging.messaging().delegate = self

        UNUserNotificationCenter.current().delegate = self

        return true
    }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        Messaging.messaging().apnsToken = deviceToken
    }
}
```

### 4\. Update your `NotificationServiceExtension`[](#4-update-your-notificationserviceextension)

You need to initialize the push module with your integration’s *CDP API Key*. Update your `NotificationServiceExtension` (using the appropriate push package, APN or FCM).

If you previously initialized the SDK using `CustomerIO.initialize`, you can remove that now. You *only* need to initialize the push package.

```swift
// use MessagingPushFCM if FCM is your push service
MessagingPushAPN.initializeForExtension(
  withConfig: MessagingPushConfigBuilder(
      cdpApiKey: "YOUR_CDP_API_KEY"
  )
  // Optional: set your Customer.io account region (.US or .EU). Default: US
  .region(.US)
  .build()
)
```

### 5\. Update your `identify`, `track`, and `screen` calls[](#5-update-your-identify-track-and-screen-calls)

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`: `data` becomes `properties`
*   `screen`: `name` becomes `title`, and `data` becomes `properties`

 New (3.x)

#### New (3.x)[](#New \(3.x\))

```swift
// CioDataPipelines replaces CioTracking 
import CioDataPipelines
//identify: identifier becomes userId, body becomes traits
CustomerIO.shared.identify(userId: "USER_ID", traits: ["age": 30])

// track: data becomes properties
CustomerIO.shared.track(name: "Purchase", properties: ["product": "shirt"])

// screen: name becomes title, data becomes properties
CustomerIO.shared.track(title: "Cart", properties: ["source": "link"])
```

 Old (2.x)

#### Old (2.x)[](#Old \(2.x\))

```swift
import CioTracking

// identify
CustomerIO.shared.identify(identifier: "USER_ID", body: ["age": 30]) {}

// track
CustomerIO.shared.track(name: "Purchase", data: ["product": "shirt"])

// screen
CustomerIO.shared.track(name: "Cart", data: ["source": "link"])
```

## Configuration Changes[](#configuration-changes)

As a part of this release, we’ve changed a few configuration options. The `MessagingInApp` and `MessagingPush` modules also now take their own configuration options.

### `DataPipelines` configuration options[](#datapipelines-configuration-options)

For the base SDK, you’ll use `SDKConfigBuilder` 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.

`autoTrackUIKitScreenViews`

boolean

`false`

Replaces `autoTrackScreenViews`; functionality is unchanged. We simply renamed the option to reflect support for UIKit and *not* SwiftUI.

`trackApplicationLifeCycleEvents`

boolean

`true`

When true, the SDK automatically tracks application lifecycle events (like *Application Installed*).

### `MessagingPush` configuration options[](#messagingpush-configuration-options)

You need to initialize the push package in both your `AppDelegate` and `NotificationServiceExtension` files. In your `AppDelegate`, you don’t need to pass options. You can simply pass `MessagingPushAPN.initialize()`.

But, in your `NotificationServiceExtension`, you’ll need to pass the `cdpApiKey` to initialize the push package.

```swift
MessagingPushAPN.initializeForExtension(withConfig: MessagingPushConfigBuilder(cdpApiKey: "CDP_API_KEY").build())
```

Option

Type

Default

Description

`region`

`.US` or `.EU`

`.US`

The region your Customer.io account resides in US or EU.

`autoFetchDeviceToken`

boolean

`true`

When `true`, the package automatically fetches the device token for push notifications.

`autoTrackPushEvents`

boolean

`true`

Automatically track `opened` and `delivered` metrics based on push notifications.

`showPushAppInForeground`

boolean

`true`

Show push notifications when the app is in the foreground. Used only if customer’s AppDelegate doesn’t implement `UNUserNotificationCenterDelegate`.

### `MessagingInApp` configuration options[](#messaginginapp-configuration-options)

When you initialize the `CioMessagingInApp` package, **you must pass both of these configuration options**.

Option

Type

Default

Description

`siteId`

string

The [Site IDEquivalent to the *user name* you’ll use to interface with the Journeys Track API; also used with our JavaScript snippets. You can find your Site ID under *Workspace Settings* > *API Credentials*](https://fly.customer.io/env/last/settings/api_credentials) from a set of Track API credentials; this determines the workspace that your app listens for in-app messages from.

`Region`

`.US` or `.EU`

`.US`

The region your Customer.io account resides in—US or EU.

---

## Whats new > 4.0.0 upgrade

**Source:** /integrations/sdk/ios/whats-new/4.0.0-upgrade

# Upgrade from 3.x to 4.0.0

This page details breaking changes from version 3.x to 4.0.0 of the SDK, specifically for users utilizing Firebase Cloud Messaging (FCM) for push notifications.

## What changed?[](#what-changed)

Version 4.0.0 introduces a breaking change for users who utilize Firebase Cloud Messaging (FCM) for push notifications. The change improves the FCM integration by consolidating Firebase dependencies into a dedicated wrapper package.

### FCM Integration Changes[](#fcm-integration-changes)

*   **New dependency required**: `CioFirebaseWrapper` package is now required for FCM push notifications
*   **Import statement updated**: Add `import CioFirebaseWrapper` wherever you use `import CioMessagingPushFCM`
*   **Initialize method unchanged**: Continue using `MessagingPushFCM.initialize()` but it now comes from the new package

**Note**: This change only affects users utilizing FCM for push notifications. If you use Apple Push Notification Service (APNs), no changes are required.

## Upgrade process[](#upgrade-process)

### 1\. Update dependencies[](#1-update-dependencies)

Update your dependency management configuration to include the new `CioFirebaseWrapper` package.

 Swift Package Manager (SPM)

#### Swift Package Manager (SPM)[](#Swift Package Manager \(SPM\))

Add the `CioFirebaseWrapper` package to your project in Xcode:

1.  In Xcode, go to **File** > **Add Package Dependencies**
2.  Add `https://github.com/customerio/customerio-ios-fcm.git` if not already added
3.  Select the `CioFirebaseWrapper` package in addition to your existing packages

 CocoaPods

#### CocoaPods[](#CocoaPods)

Add the new pod to your `Podfile`:

```ruby
# Add this new dependency for FCM push notifications
pod 'CustomerIO/CioFirebaseWrapper'

# Keep your existing dependencies
pod 'CustomerIO/DataPipelines'
pod 'CustomerIO/MessagingPushFCM'
pod 'CustomerIO/MessagingInApp'
```

Then run:

```bash
pod install
```

### 2\. Update import statements[](#2-update-import-statements)

Add `CioFirebaseWrapper` import statement in all files where you import `CioMessagingPushFCM`. Don’t change your other import statements:

```swift
import CioDataPipelines
import CioFirebaseWrapper
import CioMessagingPushFCM
import FirebaseCore
import FirebaseMessaging
import Foundation
import UIKit
```

### 3\. Verify initialization code[](#3-verify-initialization-code)

Your initialization code should remain the same. The `MessagingPushFCM.initialize()` method continues to work exactly as before, but now it comes from the `CioFirebaseWrapper` package:

```swift
// This initialization code remains unchanged
MessagingPushFCM.initialize(
    withConfig: MessagingPushConfigBuilder()
        .autoFetchDeviceToken(true)
        .build()
)
```

## Complete example[](#complete-example)

Here’s a complete example of the updated FCM setup:

```swift
import CioDataPipelines
import CioFirebaseWrapper
import CioMessagingPushFCM
import FirebaseCore
import FirebaseMessaging
import Foundation
import UIKit

@main
class AppDelegateWithCioIntegration: CioAppDelegateWrapper<AppDelegate> {}

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication, 
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {
        // Initialize Firebase
        FirebaseApp.configure()
        
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        
        // Configure and initialize the Customer.io SDK
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            .migrationSiteId(siteId)
            .autoTrackUIKitScreenViews()
            .autoTrackDeviceAttributes(true)
  
        CustomerIO.initialize(withConfig: config.build())

        // Initialize messaging features - method remains the same
        MessagingPushFCM.initialize(
            withConfig: MessagingPushConfigBuilder()
                .autoFetchDeviceToken(true)
                .build()
        )

        return true
    }
}
```

## Troubleshooting[](#troubleshooting)

### Build errors after upgrade[](#build-errors-after-upgrade)

If you encounter build errors after upgrading:

1.  **Clean your build**: In Xcode, go to **Product** > **Clean Build Folder**
2.  **Remove old packages**: If using SPM, remove the old packages and re-add them
3.  **Update CocoaPods**: If using CocoaPods, run `pod deintegrate` followed by `pod install`

### Import errors[](#import-errors)

If you see errors related to missing imports:

1.  Verify that `CioFirebaseWrapper` is properly added to your project dependencies
2.  Check that the file initializing FCM functionality has both `import CioFirebaseWrapper` and `import CioMessagingPushFCM`

---

## Whats new > 4.4.0 upgrade

**Source:** /integrations/sdk/ios/whats-new/4.4.0-upgrade

# Upgrade from 4.x to 4.4.0

Version 4.4.0 adds App Groups support for more reliable push delivery metric tracking. This update is additive—existing integrations work without modification.

## What changed?[](#what-changed)

Version 4.4.0 adds support for [App Groups](/integrations/sdk/ios/push/app-groups/), which improves the reliability of push delivery metric tracking. This update is additive—your existing integration continues to work without changes. However, to take advantage of App Groups, you’ll need to update your Xcode project configuration and regenerate provisioning profiles if you use manual signing.

### Why App Groups?[](#why-app-groups)

When you send a push notification, the SDK tracks delivery metrics in your Notification Service Extension (NSE). However, iOS can kill the NSE before the tracking request completes, which means some delivery data may never reach Customer.io. App Groups provide shared storage between your main app and the NSE, so the SDK can save metrics and recover them on the next app launch.

## Upgrade process[](#upgrade-process)

1.  Update to version 4.4.0 or later of the Customer.io iOS SDK.
2.  Follow the [App Groups setup instructions](/integrations/sdk/ios/push/app-groups/) to configure your Xcode project and pass the `.appGroupId()` to the SDK.

No other code changes are required.

---

## Whats new > Changelog

**Source:** /integrations/sdk/ios/whats-new/changelog

# Changelog

Check out release history for stable releases of iOS SDKs. 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.

4.4.14.4.04.3.14.3.04.2.04.1.24.1.14.1.04.0.03.14.03.13.13.13.03.12.03.11.03.10.93.10.83.10.73.10.63.10.53.10.43.10.33.10.23.10.13.10.03.9.03.8.23.8.13.8.03.7.23.7.13.7.03.6.03.5.13.5.03.4.13.4.03.3.33.3.23.3.13.3.03.2.33.2.23.2.13.2.03.1.33.1.23.1.13.1.03.0.23.0.13.0.02.12.52.12.42.12.32.12.22.12.12.12.02.11.12.11.02.10.22.10.12.10.02.9.22.9.12.9.02.8.52.8.42.8.32.8.22.8.12.8.02.7.82.7.72.7.62.7.52.7.42.7.32.7.22.7.12.7.02.6.12.6.02.5.32.5.22.5.12.5.02.4.12.4.02.3.02.2.02.1.22.1.12.1.02.0.6

### Breaking Changes

### Features

### Bug Fixes

# 4.x Releases[](#4x-releases)

* * *

*   ### 4.4.1[](#441)
    
    April 14, 2026[code changes](https://github.com/customerio/customerio-ios/compare/4.4.0...4.4.1)
    
    ### Bug Fixes
    
    *   Fix in-app messages not displaying when app returns from background when SSE enabled ([#1031](https://github.com/customerio/customerio-ios/issues/1031)) ([a7e586f](https://github.com/customerio/customerio-ios/commit/a7e586fca1453a8c0a6b48954c30521a6ee899fd))
    
*   ### 4.4.0[](#440)
    
    April 2, 2026[code changes](https://github.com/customerio/customerio-ios/compare/4.3.1...4.4.0)
    
    ### Features
    
    *   Push delivery reslience improvement ([#1030](https://github.com/customerio/customerio-ios/issues/1030)) ([2d6379d](https://github.com/customerio/customerio-ios/commit/2d6379d36e1cddbd7890677934586b27fc787f97))
    

*   ### 4.3.1[](#431)
    
    March 10, 2026[code changes](https://github.com/customerio/customerio-ios/compare/4.3.0...4.3.1)
    
    ### Bug Fixes
    
    *   Do not log errors when removing files that doesn’t exist ([#1022](https://github.com/customerio/customerio-ios/issues/1022)) ([f67eab4](https://github.com/customerio/customerio-ios/commit/f67eab450553950011f8f71f2814f4296db46170))
    
*   ### 4.3.0[](#430)
    
    March 9, 2026[code changes](https://github.com/customerio/customerio-ios/compare/4.2.0...4.3.0)
    
    ### Features
    
    *   Location enrichment ([#1019](https://github.com/customerio/customerio-ios/issues/1019)) ([6f0549d](https://github.com/customerio/customerio-ios/commit/6f0549d7b8db95c6452b5f510322aa990c5fec8d))
    

*   ### 4.2.0[](#420)
    
    February 20, 2026[code changes](https://github.com/customerio/customerio-ios/compare/4.1.2...4.2.0)
    
    ### Features
    
    *   Added support for Notification Inbox ([#1011](https://github.com/customerio/customerio-ios/issues/1011)) ([d84c8b6](https://github.com/customerio/customerio-ios/commit/d84c8b65569bd7598882c21c6890b4dd9d68ac68))
    

*   ### 4.1.2[](#412)
    
    February 5, 2026[code changes](https://github.com/customerio/customerio-ios/compare/4.1.1...4.1.2)
    
    ### Bug Fixes
    
    *   Crash in swizzled didRegisterForRemoteNotificationsWithDeviceToken for FCM ([#990](https://github.com/customerio/customerio-ios/issues/990)) ([13d4d43](https://github.com/customerio/customerio-ios/commit/13d4d43f725b1052c449c41bcc1f96fb5b0d935b))
    
*   ### 4.1.1[](#411)
    
    January 26, 2026[code changes](https://github.com/customerio/customerio-ios/compare/4.1.0...4.1.1)
    
    ### Bug Fixes
    
    *   Fixed intermittent test failure and crashing of the EventBusHandler ([#982](https://github.com/customerio/customerio-ios/issues/982)) ([d045741](https://github.com/customerio/customerio-ios/commit/d0457411479ab93b1618bb183ac19e4d6a17602e))
    
*   ### 4.1.0[](#410)
    
    January 13, 2026[code changes](https://github.com/customerio/customerio-ios/compare/4.0.0...4.1.0)
    
    ### Features
    
    *   In-app messages now support SSE (Server-Sent Events) as an alternative to polling, reducing latency and improving message delivery efficiency ([#975](https://github.com/customerio/customerio-ios/issues/975)) ([c8345c5](https://github.com/customerio/customerio-ios/commit/c8345c526d3bdb83dd77428a7d433191b01c4aba))
    

*   ### 4.0.0[](#400)
    
    October 16, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.14.0...4.0.0)
    
    ## [4.0.0](https://github.com/customerio/customerio-ios/compare/3.14.0...4.0.0) (2025-10-16)
    
    ### ⚠ BREAKING CHANGES
    
    *   Move Firebase direct depdency out of main iOS SDK ([#960](https://github.com/customerio/customerio-ios/issues/960)) ([361964e](https://github.com/customerio/customerio-ios/commit/361964edc4628590ad896dcbf6a3b1ca82cd20bb))
    

# 3.x Releases[](#3x-releases)

* * *

*   ### 3.14.0[](#3140)
    
    October 8, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.13.1...3.14.0)
    
    ### Features
    
    *   You can now send banners, modals, pop-ups, and surveys to anonymous visitors —no ID or email required.([#956](https://github.com/customerio/customerio-ios/issues/956)) ([ca93707](https://github.com/customerio/customerio-ios/commit/ca93707b5471ea5ceb0ec40a9a78e32dcb8eda26))
    

*   ### 3.13.1[](#3131)
    
    August 27, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.13.0...3.13.1)
    
    ### Bug Fixes
    
    *   Fixed build issues on Xcode 26 beta that only affected apps using CocoaPods ([#949](https://github.com/customerio/customerio-ios/issues/949)) ([f5674b5](https://github.com/customerio/customerio-ios/commit/f5674b5ef7eedcbe9f8512a654e8fc0b773b8447))
    *   Fixed an issue where custom scheme URLs were not opening when using FCM with `CioAppDelegateWrapper` ([#950](https://github.com/customerio/customerio-ios/pull/950)) ([795bdf1](https://github.com/customerio/customerio-ios/commit/795bdf126260332a05f6b41f1619dfc2ce778e02))
    
*   ### 3.13.0[](#3130)
    
    August 21, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.12.0...3.13.0)
    
    ### Features
    
    *   Align public API with other CIO SDK platforms ([#948](https://github.com/customerio/customerio-ios/issues/948)) ([b63efd7](https://github.com/customerio/customerio-ios/commit/b63efd789f27afe803a879e500ed28fabb344f14))
    

*   ### 3.12.0[](#3120)
    
    August 18, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.11.0...3.12.0)
    
    ### Features
    
    *   Added support for queue sticky sessions ([#946](https://github.com/customerio/customerio-ios/issues/946)) ([4b42003](https://github.com/customerio/customerio-ios/commit/4b420032758794ed2874aa921e1907452a29c9d6))
    

*   ### 3.11.0[](#3110)
    
    July 22, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.10.9...3.11.0)
    
    ### ⚠️ BREAKING TOOLING CHANGES
    
    *   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 ([#938](https://github.com/customerio/customerio-ios/issues/938)) ([3a417ec](https://github.com/customerio/customerio-ios/commit/3a417ecc8d77cb77b0cf38c2b6d77d22c884b8d9))
    

*   ### 3.10.9[](#3109)
    
    July 21, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.10.8...3.10.9)
    
    ### Bug Fixes
    
    *   Fixed build issue by adding a default implementation for an internal SPI method in `DeepLinkUtil`, preventing conformance errors with `BUILD_LIBRARY_FOR_DISTRIBUTION = YES` ([#936](https://github.com/customerio/customerio-ios/issues/936)) ([c7ba900](https://github.com/customerio/customerio-ios/commit/c7ba900e4e5d1c17e8fd598b645784d2f7e95c00))
    
*   ### 3.10.8[](#3108)
    
    July 8, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.10.7...3.10.8)
    
    ### Bug Fixes
    
    *   Preserve numeric types when doing sanitization for JSON ([#935](https://github.com/customerio/customerio-ios/issues/935)) ([cbf95fd](https://github.com/customerio/customerio-ios/commit/cbf95fdbdf72142f5b711ce9e0cb0ad81835ff41))
    
*   ### 3.10.7[](#3107)
    
    June 26, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.10.6...3.10.7)
    
    ### Internal
    
    *   Added base classes around Inline views to support communication of inline view with Wrapper SDKs ([#933](https://github.com/customerio/customerio-ios/issues/933)) ([2a4dbb9](https://github.com/customerio/customerio-ios/commit/2a4dbb98a8cfe6a3eea6e5fd6adf61a0015d0fb8))
    
*   ### 3.10.6[](#3106)
    
    June 25, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.10.5...3.10.6)
    
    ### Bug Fixes
    
    *   Sanitize unsupported numeric values ([#930](https://github.com/customerio/customerio-ios/issues/930)) ([2a848e4](https://github.com/customerio/customerio-ios/commit/2a848e4ad5b2e9b215f23339bf29c9ffddd6d18e))
    
*   ### 3.10.5[](#3105)
    
    June 4, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.10.4...3.10.5)
    
    ### Bug Fixes
    
    *   Fix in-app messages not overlaying safe area top and bottom ([#925](https://github.com/customerio/customerio-ios/issues/925)) ([8ed7d7b](https://github.com/customerio/customerio-ios/commit/8ed7d7b828a38eb4c6bdffd5db0ccaa5ddf0cd72))
    
*   ### 3.10.4[](#3104)
    
    June 2, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.10.3...3.10.4)
    
    ### Bug Fixes
    
    *   Fixed an issue where the SDK enforced a strict version of firebase messaging, preventing integration with newer versions. ([#929](https://github.com/customerio/customerio-ios/issues/929)) ([a0211d3](https://github.com/customerio/customerio-ios/commit/a0211d32739bc54eb6e503e4ad1846d14ddaa241))
    
*   ### 3.10.3[](#3103)
    
    May 30, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.10.2...3.10.3)
    
    ### Bug Fixes
    
    *   premature deallocation of the rich push http client ([1edb75c](https://github.com/customerio/customerio-ios/commit/1edb75c141fbc20e42266f2a27c0ea0ff5fdb7e9))
    *   premature deallocation of the rich push http client ([#924](https://github.com/customerio/customerio-ios/issues/924)) ([9a985c1](https://github.com/customerio/customerio-ios/commit/9a985c1830990a8a2e68a44da5f7dbf4b7f47f62))
    
*   ### 3.10.2[](#3102)
    
    May 27, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.10.1...3.10.2)
    
    ### Bug Fixes
    
    *   Fixes the bug where multi screen in-app messages might dismiss earlier ([#920](https://github.com/customerio/customerio-ios/issues/920)) ([0426e06](https://github.com/customerio/customerio-ios/commit/0426e0614c20b5aea68c8faebcdd60a5a557959a))
    
*   ### 3.10.1[](#3101)
    
    May 26, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.10.0...3.10.1)
    
    ### Bug Fixes
    
    *   Fixed an issue that caused duplicate internal calls for setting device tokens ([#922](https://github.com/customerio/customerio-ios/issues/922)) ([0f5d7f0](https://github.com/customerio/customerio-ios/commit/0f5d7f01bdeaeaf8fb324367a5f0df110f64e301))
    *   Improved detection of UNUserNotificationCenterDelegate method implementations by explicitly tracking completion handler calls, ensuring more reliable notification handling across different integration scenarios ([#922](https://github.com/customerio/customerio-ios/issues/922)) ([0f5d7f0](https://github.com/customerio/customerio-ios/commit/0f5d7f01bdeaeaf8fb324367a5f0df110f64e301))
    
*   ### 3.10.0[](#3100)
    
    May 23, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.9.0...3.10.0)
    
    ⚠ BREAKING TOOLING CHANGES
    
    *   Support for FCM `11.12.0` ([#918](https://github.com/customerio/customerio-ios/issues/918)) ([dcc21e0](https://github.com/customerio/customerio-ios/commit/dcc21e0a0f881b31057c379a935ab24a948d0069)) is added which requires Xcode 16.2 or later, so you will need to update xcode if you are utilizing the FCM module.
    

*   ### 3.9.0[](#390)
    
    May 15, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.8.2...3.9.0)
    
    # Improvements
    
    *   **Swizzling replacement**:
        *   We’ve replaced the old “swizzling” method for handling push notifications with a clearer, more reliable system.
        *   New AppDelegate classes (`CioAppDelegate` and `CioAppDelegateWrapper`) make integration easier, more explicit, and more maintainable.
    *   **Deep Linking**:
        *   Added a new `deepLinkingCallback` to fix issues with some libraries (like Firebase) that don’t properly handle `application(_:continue:restorationHandler:)`.
        *   This ensures deep links work more consistently across different setups.
    *   **Sample Applications**:
        *   Updated to show how to implement the new AppDelegate setup and deep linking — making it easier to follow and integrate.
    
    # Deprecations
    
    *   The old swizzling-based push notification setup is now deprecated. It still works, but we recommend switching to the new method as it will be removed in a future release.
    *   The previous deep-linking approach is also deprecated, replaced by the new `deepLinkingCallback`.
    

*   ### 3.8.2[](#382)
    
    April 22, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.8.1...3.8.2)
    
    ### Bug Fixes
    
    *   Parse region leniently and fall back to default region if parsing fails instead of blocking initialization ([#879](https://github.com/customerio/customerio-ios/pull/879))
    *   Hardcoded the upper limit of FCM version to avoid the issues with compilation of Cocoapods SDK (#880)
    
*   ### 3.8.1[](#381)
    
    April 2, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.8.0...3.8.1)
    
    ### Bug Fixes
    
    *   Fixed an issue where the “Application Installed” event was incorrectly triggered on every app launch instead of only after the initial installation.
    
    ### Improvement
    
    *   Updated libraries to the latest versions to improve stability.
    
*   ### 3.8.0[](#380)
    
    January 30, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.7.2...3.8.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. ([#864](https://github.com/customerio/customerio-ios/issues/864)) ([35fba9b](https://github.com/customerio/customerio-ios/commit/35fba9b8171ca50138553d2c2d6a87a747a90c49))
    
    ### Fixes
    
    *   Incorrectly scrolling content for in-app modal messages positioned top/bottom. #858
    

*   ### 3.7.2[](#372)
    
    January 8, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.7.1...3.7.2)
    
    ### Bug Fixes
    
    *   Fixes in-app messages overlay background color being ignored from in-app message payload ([#843](https://github.com/customerio/customerio-ios/issues/843))
    
*   ### 3.7.1[](#371)
    
    January 6, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.7.0...3.7.1)
    
    ### Bug Fixes
    
    *   Fixes the intermittent crash with in-app messages when triggered on background thread ([#840](https://github.com/customerio/customerio-ios/issues/840)) ([93200a4](https://github.com/customerio/customerio-ios/commit/93200a420d589962d12c618f63b64dce03c8940b))
    
*   ### 3.7.0[](#370)
    
    January 1, 2025[code changes](https://github.com/customerio/customerio-ios/compare/3.6.0...3.7.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 just updating SDK configuration during initialization. ([#844](https://github.com/customerio/customerio-ios/issues/844)) ([486e228](https://github.com/customerio/customerio-ios/commit/486e2281f91715fcbcfa9dbde01743329e838fda))
    
    ### Fixes
    
    *   Handle fragments (#) when you open a web page from in-app via “Open the web” action. #842
    

*   ### 3.6.0[](#360)
    
    November 13, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.5.1...3.6.0)
    
    ### Features
    
    *   This release introduces support for displaying larger in-app messages. ([#831](https://github.com/customerio/customerio-ios/issues/831)) ([3731f68](https://github.com/customerio/customerio-ios/commit/3731f685a19bd1e9840c53ab3bcd70e37deac182))
    
    ### Fixes
    
    *   Resolve an issue that prevented push metrics from being reported for customers who set the region to the EU region. ([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. ([https://github.com/customerio/customerio-ios/pull/834](https://github.com/customerio/customerio-ios/pull/834))
    

*   ### 3.5.1[](#351)
    
    October 25, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.5.0...3.5.1)
    
    ### Bug Fixes
    
    *   This fix dismisses keyboard when an in-app message appears to allow uninterrupted user interaction ([#828](https://github.com/customerio/customerio-ios/issues/828)) ([faad28a](https://github.com/customerio/customerio-ios/commit/faad28a02f085b5f4a334bc9122dbfe862e38119))
    
*   ### 3.5.0[](#350)
    
    October 16, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.4.1...3.5.0)
    
    ### Improvement
    
    *   This release includes changes needed to support for data pipelines in the our React Native SDK. There are no new features for customers utilizing our native Android SDK only. ([#819](https://github.com/customerio/customerio-ios/issues/819)) ([171dfdc](https://github.com/customerio/customerio-ios/commit/171dfdc45a43f9560f24ae2d7ea2dcd3b71297c8))
    

*   ### 3.4.1[](#341)
    
    October 1, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.4.0...3.4.1)
    
    ### Features
    
    *   This update extends support to newer Firebase SDK versions. ([#792](https://github.com/customerio/customerio-ios/pull/817))
    
    ### Bug Fixes
    
    *   Fixes an edge case where the in-app queue would not maintain its integrity when `autoScreenTracking` is turned on and screens are frequently changed. ([#820](https://github.com/customerio/customerio-ios/issues/820)) ([62268a4](https://github.com/customerio/customerio-ios/commit/62268a4a3248ba62f7898f6a0cb25e9e7cb5d015))
    
*   ### 3.4.0[](#340)
    
    September 17, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.3.3...3.4.0)
    
    ### Features
    
    *   Revamped the in-app messaging module to enhance performance by optimizing local processing, accelerating message loading during page transitions, and implementing a state-driven architecture for better responsiveness and control. \[[#815](https://github.com/customerio/customerio-ios/issues/815)\] ([5cd651b](https://github.com/customerio/customerio-ios/commit/5cd651b0cc9a3593bdc1e782565bcf89ede87eb3))
    *   We highly recommend upgrading to the new in-app editor to ensure optimal performance.
    
    ### Fixes
    
    *   We reduced the chance of duplicate in-app messages by keeping a local store of messages already displayed.
    

*   ### 3.3.3[](#333)
    
    August 8, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.3.2...3.3.3)
    
    ### Bug Fixes
    
    *   Resolved issue for customers who use the tool, Tuist, to install the Customer.io iOS SDK. ([#791](https://github.com/customerio/customerio-ios/issues/791)) ([cd1346a](https://github.com/customerio/customerio-ios/commit/cd1346a8ee9bd7c0b81c7e92d4ffc7507a904d9f))
    
*   ### 3.3.2[](#332)
    
    June 27, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.3.1...3.3.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. ([#751](https://github.com/customerio/customerio-ios/issues/751)) ([ca5abb3](https://github.com/customerio/customerio-ios/commit/ca5abb349f4006a6de5f5b9c1355c7f422877c7c))
    
*   ### 3.3.1[](#331)
    
    June 25, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.3.0...3.3.1)
    
    ### Bug Fixes
    
    *   This release resolves a scenario where in-app messages with page rules enabled are sent to a device but is never shown if the app is killed and re-opened. ([#746](https://github.com/customerio/customerio-ios/issues/746)) ([cfae57d](https://github.com/customerio/customerio-ios/commit/cfae57d1b675865ca980c22f52abaeee9c9baa33))
    
*   ### 3.3.0[](#330)
    
    June 17, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.2.3...3.3.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. ([#731](https://github.com/customerio/customerio-ios/issues/731)) ([cb1d014](https://github.com/customerio/customerio-ios/commit/cb1d014d52f7c53af6650e185857ba0e5468db49))
    

*   ### 3.2.3[](#323)
    
    June 13, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.2.2...3.2.3)
    
    ### Bug Fixes
    
    *   For users of our [Flutter SDK](https://github.com/customerio/customerio-flutter) and [React Native SDK](https://github.com/customerio/customerio-reactnative), 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. ([#736](https://github.com/customerio/customerio-ios/issues/736)) ([ccb0f47](https://github.com/customerio/customerio-ios/commit/ccb0f47297cca52c5023d149a325d9099dbdccd8))
    
*   ### 3.2.2[](#322)
    
    May 29, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.2.1...3.2.2)
    
    ### Bug Fixes
    
    *   Resolves an issue that prevented actions from being tracked for in-app messages. ([#728](https://github.com/customerio/customerio-ios/issues/728)) ([c1f69c1](https://github.com/customerio/customerio-ios/commit/c1f69c167205238673265eda60aa5183bf14181a))
    
*   ### 3.2.1[](#321)
    
    May 27, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.2.0...3.2.1)
    
    ### Bug Fixes
    
    *   Resolves an issue that caused a crash on the emulator while in developing. ([#725](https://github.com/customerio/customerio-ios/issues/725)) ([7ca2dc2](https://github.com/customerio/customerio-ios/commit/7ca2dc2fb8b65bb5f01dbbe7fc14ec2c0b1b7031))
    
*   ### 3.2.0[](#320)
    
    May 7, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.1.3...3.2.0)
    
    ### Features
    
    *   Segment compatibility ([#685](https://github.com/customerio/customerio-ios/issues/685)) ([7298783](https://github.com/customerio/customerio-ios/commit/7298783adb888c014f30f04eb4d20e4bc6e9d810))
    

*   ### 3.1.3[](#313)
    
    April 24, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.1.2...3.1.3)
    
    ### Bug Fixes
    
    *   update segment internal SPM dependencies with fixed version ([#708](https://github.com/customerio/customerio-ios/issues/708)) ([c02742e](https://github.com/customerio/customerio-ios/commit/c02742eed420bd40d87bf4bf22d6a7e7727cdaab))
    
*   ### 3.1.2[](#312)
    
    April 16, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.1.1...3.1.2)
    
    ### Bug Fixes
    
    *   track bq migration for identify profile events with no attributes ([2fb4827](https://github.com/customerio/customerio-ios/commit/2fb4827ff0e59329e5bb2fc185b0e5781544c58a))
    
*   ### 3.1.1[](#311)
    
    April 15, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.1.0...3.1.1)
    
    ### Bug Fixes
    
    *   added alias for Region ([#693](https://github.com/customerio/customerio-ios/issues/693)) ([1619c72](https://github.com/customerio/customerio-ios/commit/1619c72e9acbf0b95fa0d7e5f8e25bf4fd2fc1d2))
    
*   ### 3.1.0[](#310)
    
    April 10, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.0.2...3.1.0)
    
    ### Features
    
    *   privacy manifest files ([#677](https://github.com/customerio/customerio-ios/issues/677)) ([e5c6dc7](https://github.com/customerio/customerio-ios/commit/e5c6dc7b6841ca31c02f2bd61cd49dc9c6604055))
    

*   ### 3.0.2[](#302)
    
    April 9, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.0.1...3.0.2)
    
    ### Bug Fixes
    
    *   \[visionos\] Minor project cleanup ([#688](https://github.com/customerio/customerio-ios/issues/688)) ([d2934fe](https://github.com/customerio/customerio-ios/commit/d2934fe1ad5255f35568411ee0c5362c052cd7ed))
    
*   ### 3.0.1[](#301)
    
    March 21, 2024[code changes](https://github.com/customerio/customerio-ios/compare/3.0.0...3.0.1)
    
    ### Bug Fixes
    
    *   install 3.0 with Swift Package Manager from Xcode ([#660](https://github.com/customerio/customerio-ios/issues/660)) ([45b0367](https://github.com/customerio/customerio-ios/commit/45b0367451b81aae952ee4f248226be48a37650f))
    
*   ### 3.0.0[](#300)
    
    March 20, 2024[code changes](https://github.com/customerio/customerio-ios/compare/2.12.5...3.0.0)
    
    ### ⚠ BREAKING CHANGES
    
    *   iOS as a source for Data Pipelines (#659)
    
    ### Features
    
    *   iOS as a source for Data Pipelines ([#659](https://github.com/customerio/customerio-ios/issues/659)) ([0a68373](https://github.com/customerio/customerio-ios/commit/0a683732a8eea6372445b30c4bcce2f1dc9caa86))
    *   migration module to cater to all migration tasks ([#530](https://github.com/customerio/customerio-ios/issues/530)) ([2feb1d4](https://github.com/customerio/customerio-ios/commit/2feb1d4af49d1cf80ef1eecacb7ce2ea0a7881c9))
    
    ### Bug Fixes
    
    *   add attributes to properties ([#649](https://github.com/customerio/customerio-ios/issues/649)) ([4b02e92](https://github.com/customerio/customerio-ios/commit/4b02e926e64ad3f26b111a7b7ff6a421d71203d9))
    *   all sdk modules can only be initialized once ([ae46c7f](https://github.com/customerio/customerio-ios/commit/ae46c7f01c73d5db5a40d23f5959be7bf83c2e93))
    *   app crash on identify method ([#458](https://github.com/customerio/customerio-ios/issues/458)) ([13e9862](https://github.com/customerio/customerio-ios/commit/13e9862b3e8161185e7f5dd823297187eb79d78a))
    *   compilation for test ([f14b773](https://github.com/customerio/customerio-ios/commit/f14b77331c372689cc8d1791b625500ab4b3182b))
    *   compilation issue ([420a61e](https://github.com/customerio/customerio-ios/commit/420a61e40d7a899bca7549390cbc33fbff16d7c4))
    *   eventbus handler ref ([#469](https://github.com/customerio/customerio-ios/issues/469)) ([8c8ef91](https://github.com/customerio/customerio-ios/commit/8c8ef91bfbd7722ecd911f6be83a7ec5e1cc0725))
    *   journey id in migration payload ([#653](https://github.com/customerio/customerio-ios/issues/653)) ([3b649c9](https://github.com/customerio/customerio-ios/commit/3b649c905946af1922665b183007b008d8bc0274))
    *   prevent duplicate automatic screenview events from being tracked ([fea9ec5](https://github.com/customerio/customerio-ios/commit/fea9ec5ea498e25b24d65ef9e619790102337a0d))
    *   pushEventHandler test ([dc80fc2](https://github.com/customerio/customerio-ios/commit/dc80fc2d66c290ad7080ac8e05a4cded2f729071))
    *   remove occurrence of autoTrackDeviceAttributes from all push modules ([#505](https://github.com/customerio/customerio-ios/issues/505)) ([8dc6507](https://github.com/customerio/customerio-ios/commit/8dc6507582f1aed97fb80229df152f2ec152b186))
    *   removed last\_used from properties ([#477](https://github.com/customerio/customerio-ios/issues/477)) ([b0b9631](https://github.com/customerio/customerio-ios/commit/b0b9631d88c558206f88d7de161ec51c7a15c2cf))
    *   sample app issues ([#551](https://github.com/customerio/customerio-ios/issues/551)) ([05544b3](https://github.com/customerio/customerio-ios/commit/05544b3014b5276d21be736a9618046321967f23))
    *   use git commit instead of git branch for segment dependency ([d245015](https://github.com/customerio/customerio-ios/commit/d2450154725c54898b5d08defacdd2e2f37406e8))
    

# 2.x Releases[](#2x-releases)

* * *

*   ### 2.12.5[](#2125)
    
    March 18, 2024[code changes](https://github.com/customerio/customerio-ios/compare/2.12.4...2.12.5)
    
    ### Bug Fixes
    
    *   do not bundle .md files in cocoapods deployments ([15dbb48](https://github.com/customerio/customerio-ios/commit/15dbb48b942008178f5d6433fe9fc144f7c740b9))
    
*   ### 2.12.4[](#2124)
    
    March 13, 2024[code changes](https://github.com/customerio/customerio-ios/compare/2.12.3...2.12.4)
    
    ### Bug Fixes
    
    *   call completionHandler if push handler does not implement optional function ([9996872](https://github.com/customerio/customerio-ios/commit/9996872e774a0c0c2135a0e02a398c07ab2397f8))
    *   forward push received app in foreground events to 3rd party callbacks ([bd55e2b](https://github.com/customerio/customerio-ios/commit/bd55e2b9b2aa349d7f78c1e76d22dfa1463e35ac))
    *   forward push response events to 3rd party callback functions, for CIO push ([121b157](https://github.com/customerio/customerio-ios/commit/121b157457708a6ac9f971a367f85d37a1684bde))
    *   open deep link from main thread as required by UIKit ([7d7867f](https://github.com/customerio/customerio-ios/commit/7d7867fa0265169daa4e62bd016dbfb3f1764d65))
    
*   ### 2.12.3[](#2123)
    
    March 5, 2024[code changes](https://github.com/customerio/customerio-ios/compare/2.12.2...2.12.3)
    
    ### Bug Fixes
    
    *   expo users reported app crash on didFailToRegisterForRemoteNotificationsWithError ([#575](https://github.com/customerio/customerio-ios/issues/575)) ([ac70292](https://github.com/customerio/customerio-ios/commit/ac702920fc205354715a99985eb5469f5a3d99da))
    
*   ### 2.12.2[](#2122)
    
    February 23, 2024[code changes](https://github.com/customerio/customerio-ios/compare/2.12.1...2.12.2)
    
    ### Bug Fixes
    
    *   rich push image downloading path conflict when its already downloaded ([#561](https://github.com/customerio/customerio-ios/issues/561)) ([67cbfd6](https://github.com/customerio/customerio-ios/commit/67cbfd6b7aeb46dae8700f4bfb94817e06ad002e))
    
*   ### 2.12.1[](#2121)
    
    February 15, 2024[code changes](https://github.com/customerio/customerio-ios/compare/2.12.0...2.12.1)
    
    ### Bug Fixes
    
    *   fixes an issue with multiple timers being scheduled ([#529](https://github.com/customerio/customerio-ios/issues/529)) ([ec383f1](https://github.com/customerio/customerio-ios/commit/ec383f1f92d0ecf819e90a87879c9ca870e0017d))
    
*   ### 2.12.0[](#2120)
    
    February 15, 2024[code changes](https://github.com/customerio/customerio-ios/compare/2.11.1...2.12.0)
    
    ### Features
    
    *   use new header to set polling interval ([#519](https://github.com/customerio/customerio-ios/issues/519)) ([05a1ebd](https://github.com/customerio/customerio-ios/commit/05a1ebd475b965974b9aae60895cdaf8ac11e113))
    

*   ### 2.11.1[](#2111)
    
    February 14, 2024[code changes](https://github.com/customerio/customerio-ios/compare/2.11.0...2.11.1)
    
    ### Bug Fixes
    
    *   enable logging for gist ([#524](https://github.com/customerio/customerio-ios/issues/524)) ([c786897](https://github.com/customerio/customerio-ios/commit/c786897bf9ce1ba15b0c2a1c23730346f9b1e70d))
    
*   ### 2.11.0[](#2110)
    
    February 12, 2024[code changes](https://github.com/customerio/customerio-ios/compare/2.10.2...2.11.0)
    
    ### Features
    
    *   automatic push click handling ([#403](https://github.com/customerio/customerio-ios/issues/403)) ([47e94c4](https://github.com/customerio/customerio-ios/commit/47e94c47b562380afbe1e727c90c9a51cdb9e02b))
    

*   ### 2.10.2[](#2102)
    
    January 18, 2024[code changes](https://github.com/customerio/customerio-ios/compare/2.10.1...2.10.2)
    
    ### Bug Fixes
    
    *   ensure messages are only shown once ([#468](https://github.com/customerio/customerio-ios/issues/468)) ([5cc6625](https://github.com/customerio/customerio-ios/commit/5cc66250347a2202297287b989cf1993a8c4248f))
    
*   ### 2.10.1[](#2101)
    
    November 22, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.10.0...2.10.1)
    
    ### Bug Fixes
    
    *   cocoapods APN customers app not compiling ([#414](https://github.com/customerio/customerio-ios/issues/414)) ([e7aaa23](https://github.com/customerio/customerio-ios/commit/e7aaa23320bde6fff8aa35a1def9c6ba05602c46))
    
*   ### 2.10.0[](#2100)
    
    November 8, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.9.2...2.10.0)
    
    ### Features
    
    *   auto fetch device token and auto register device (apns) ([#391](https://github.com/customerio/customerio-ios/issues/391)) ([2fce84d](https://github.com/customerio/customerio-ios/commit/2fce84d0fc7801ddefcd1085b9a17b0859165d3b))
    

*   ### 2.9.2[](#292)
    
    November 7, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.9.1...2.9.2)
    
    ### Bug Fixes
    
    *   memory exception during SDK initialization async tasks ([#399](https://github.com/customerio/customerio-ios/issues/399)) ([3404523](https://github.com/customerio/customerio-ios/commit/3404523677bcf4a184b50ba88390596a3615bb6e))
    
*   ### 2.9.1[](#291)
    
    November 7, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.9.0...2.9.1)
    
    ### Bug Fixes
    
    *   add priority field to message and sorting by priority within local store ([#401](https://github.com/customerio/customerio-ios/issues/401)) ([8315118](https://github.com/customerio/customerio-ios/commit/83151189b7fa6829bf4eca47431e8aeba2c943fd))
    
*   ### 2.9.0[](#290)
    
    October 31, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.8.5...2.9.0)
    
    ### Features
    
    *   get currently registered device token ([#398](https://github.com/customerio/customerio-ios/issues/398)) ([cdedab0](https://github.com/customerio/customerio-ios/commit/cdedab0fa9a9894789f4807fc90e7b7119ff9ae9))
    

*   ### 2.8.5[](#285)
    
    October 27, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.8.4...2.8.5)
    
    ### Bug Fixes
    
    *   when queue cannot find task, expect queue runs next task ([#397](https://github.com/customerio/customerio-ios/issues/397)) ([01ea7a6](https://github.com/customerio/customerio-ios/commit/01ea7a62981de1df13b3eec44d8c6433b77c5653))
    
*   ### 2.8.4[](#284)
    
    October 18, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.8.3...2.8.4)
    
    ### Bug Fixes
    
    *   in-app positioning and persistence ([#393](https://github.com/customerio/customerio-ios/issues/393)) ([b5a2f18](https://github.com/customerio/customerio-ios/commit/b5a2f18688e2d6d139f25016ecd0215e4da259c6))
    
*   ### 2.8.3[](#283)
    
    October 11, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.8.2...2.8.3)
    
    ### Bug Fixes
    
    *   prevent registering device with empty identifier existing BQ tasks ([#392](https://github.com/customerio/customerio-ios/issues/392)) ([867619f](https://github.com/customerio/customerio-ios/commit/867619f4885e0c2e438b55826557c3fa5649b035))
    
*   ### 2.8.2[](#282)
    
    September 7, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.8.1...2.8.2)
    
    ### Bug Fixes
    
    *   reduce memory and cpu usage while running background queue ([#379](https://github.com/customerio/customerio-ios/issues/379)) ([87a7eed](https://github.com/customerio/customerio-ios/commit/87a7eed5dc8e101a0d1c3f0b6e83d8b7d637cfcb))
    
*   ### 2.8.1[](#281)
    
    August 31, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.8.0...2.8.1)
    
    ### Bug Fixes
    
    *   added url path encoding ([#376](https://github.com/customerio/customerio-ios/issues/376)) ([fbfa384](https://github.com/customerio/customerio-ios/commit/fbfa384428fdcf3ea69d623e9728a76c4d5d0416))
    
*   ### 2.8.0[](#280)
    
    August 31, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.7.8...2.8.0)
    
    ### Features
    
    *   filter automatic screenview tracking events ([#367](https://github.com/customerio/customerio-ios/issues/367)) ([1a535f9](https://github.com/customerio/customerio-ios/commit/1a535f96761fbeb1a31a7d0820cab77ec4e12c5f))
    

*   ### 2.7.8[](#278)
    
    August 14, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.7.7...2.7.8)
    
    ### Bug Fixes
    
    *   cache queue inventory for lower CPU usage while running queue ([#368](https://github.com/customerio/customerio-ios/issues/368)) ([fdcb24c](https://github.com/customerio/customerio-ios/commit/fdcb24c4f68ca027458036920b09961e819af644))
    
*   ### 2.7.7[](#277)
    
    July 26, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.7.6...2.7.7)
    
    ### Bug Fixes
    
    *   support json array in attributes ([#358](https://github.com/customerio/customerio-ios/issues/358)) ([a634358](https://github.com/customerio/customerio-ios/commit/a63435833b57f2a1405d018f158c1e945bf6c73d))
    
*   ### 2.7.6[](#276)
    
    July 21, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.7.5...2.7.6)
    
    ### Bug Fixes
    
    *   apps initializing sdk multiple times in short amount of time may make SDK ignore requests ([#360](https://github.com/customerio/customerio-ios/issues/360)) ([09829e0](https://github.com/customerio/customerio-ios/commit/09829e0c55b3637d82059087bf72b1f1d14b0e59))
    
*   ### 2.7.5[](#275)
    
    July 20, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.7.4...2.7.5)
    
    ### Bug Fixes
    
    *   deinit cleanup repo bad memory access ([#356](https://github.com/customerio/customerio-ios/issues/356)) ([0483fb0](https://github.com/customerio/customerio-ios/commit/0483fb04e02228bfe1dd1ff88e9e51e688b477fd))
    
*   ### 2.7.4[](#274)
    
    July 17, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.7.3...2.7.4)
    
    ### Bug Fixes
    
    *   save device token in SDK even if no profile identified ([#354](https://github.com/customerio/customerio-ios/issues/354)) ([a49f72c](https://github.com/customerio/customerio-ios/commit/a49f72ce94549d4e9d754891e3b6f6b309f46e8a))
    
*   ### 2.7.3[](#273)
    
    July 17, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.7.2...2.7.3)
    
    ### Bug Fixes
    
    *   prevent api calls when identifier is empty ([#353](https://github.com/customerio/customerio-ios/issues/353)) ([10b5db7](https://github.com/customerio/customerio-ios/commit/10b5db739444074847fb6e58e6554411e0a5fa74))
    
*   ### 2.7.2[](#272)
    
    July 12, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.7.1...2.7.2)
    
    ### Bug Fixes
    
    *   gist migration to CIO ([#338](https://github.com/customerio/customerio-ios/issues/338)) ([#351](https://github.com/customerio/customerio-ios/issues/351)) ([5520a7c](https://github.com/customerio/customerio-ios/commit/5520a7ceb4d35170b4bf2d2493ceb9576ea8d3cf))
    
*   ### 2.7.1[](#271)
    
    July 4, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.7.0...2.7.1)
    
    ### Bug Fixes
    
    *   bad memory access crash ([#342](https://github.com/customerio/customerio-ios/issues/342)) ([b83e6bd](https://github.com/customerio/customerio-ios/commit/b83e6bda1a0746eab65bb3d07887c3fcbcb67712))
    
*   ### 2.7.0[](#270)
    
    June 21, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.6.1...2.7.0)
    
    ### Features
    
    *   include FCM SDK as a dependency to make FCM push setup easier ([#333](https://github.com/customerio/customerio-ios/issues/333)) ([233fc22](https://github.com/customerio/customerio-ios/commit/233fc228f768fb9e1a98dffd2b5c9d49d34c9cc2))
    

*   ### 2.6.1[](#261)
    
    May 26, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.6.0...2.6.1)
    
    ### Bug Fixes
    
    *   internal module Common clashing with other Common modules in customers app ([#328](https://github.com/customerio/customerio-ios/issues/328)) ([817dd56](https://github.com/customerio/customerio-ios/commit/817dd5692c412e91b1edbac9af64c0f4844a4cb4))
    
*   ### 2.6.0[](#260)
    
    May 26, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.5.3...2.6.0)
    
    ### Features
    
    *   in-app dismiss message ([#320](https://github.com/customerio/customerio-ios/issues/320)) ([e067001](https://github.com/customerio/customerio-ios/commit/e067001628423a2dc4d9a5c1836a9d985cf60150))
    

*   ### 2.5.3[](#253)
    
    May 26, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.5.2...2.5.3)
    
    ### Bug Fixes
    
    *   in-app universal link redirection support ([#329](https://github.com/customerio/customerio-ios/issues/329)) ([51470e8](https://github.com/customerio/customerio-ios/commit/51470e80270092da1948f04a388aedaf0f2fcf35))
    
*   ### 2.5.2[](#252)
    
    May 19, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.5.1...2.5.2)
    
    ### Bug Fixes
    
    *   exact version for gist ([#321](https://github.com/customerio/customerio-ios/issues/321)) ([4b75cc5](https://github.com/customerio/customerio-ios/commit/4b75cc53e8f15a5496df17e825e12b4f44c3efef))
    
*   ### 2.5.1[](#251)
    
    May 12, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.5.0...2.5.1)
    
    ### Bug Fixes
    
    *   sdk wrappers not having device token registered because of application lifecycle ([#285](https://github.com/customerio/customerio-ios/issues/285)) ([da7fc51](https://github.com/customerio/customerio-ios/commit/da7fc512e9308af208de8dea5aeaf95839a52fc1))
    
*   ### 2.5.0[](#250)
    
    April 27, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.4.1...2.5.0)
    
    ### Features
    
    *   expose current SDK config options for reference ([#298](https://github.com/customerio/customerio-ios/issues/298)) ([6ac739b](https://github.com/customerio/customerio-ios/commit/6ac739b1550636db10230d8b2c10783f46626250))
    

*   ### 2.4.1[](#241)
    
    April 27, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.4.0...2.4.1)
    
    ### Bug Fixes
    
    *   auto update gist in CocoaPods ([#303](https://github.com/customerio/customerio-ios/issues/303)) ([6096d17](https://github.com/customerio/customerio-ios/commit/6096d176cdc52e7e8a949960bfb9c4aa1541c8fd))
    
*   ### 2.4.0[](#240)
    
    April 27, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.3.0...2.4.0)
    
    ### Features
    
    *   get the version of the SDK ([#299](https://github.com/customerio/customerio-ios/issues/299)) ([38a6b00](https://github.com/customerio/customerio-ios/commit/38a6b0061f0f5b317f592ec885f8746c65ab8ba5))
    

*   ### 2.3.0[](#230)
    
    April 19, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.2.0...2.3.0)
    
    ### Features
    
    *   in app click tracking ([#284](https://github.com/customerio/customerio-ios/issues/284)) ([4ed8edb](https://github.com/customerio/customerio-ios/commit/4ed8edbd0f9e7a7aa6a708e525c6081e93658e98))
    

*   ### 2.2.0[](#220)
    
    April 18, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.1.2...2.2.0)
    
    ### Features
    
    *   ui for new sample app with apns ([#282](https://github.com/customerio/customerio-ios/issues/282)) ([06e4a6b](https://github.com/customerio/customerio-ios/commit/06e4a6b71554610c7e7d3b95f62d129d934414be))
    

*   ### 2.1.2[](#212)
    
    March 10, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.1.1...2.1.2)
    
    ### Bug Fixes
    
    *   delete tasks that returns http 400 ([#276](https://github.com/customerio/customerio-ios/issues/276)) ([aabfe9e](https://github.com/customerio/customerio-ios/commit/aabfe9ec02ec5b40326c60e973cdfa5f9338f85f))
    
*   ### 2.1.1[](#211)
    
    March 8, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.1.0...2.1.1)
    
    ### Bug Fixes
    
    *   cocoapods app extension targets able to compile ([#277](https://github.com/customerio/customerio-ios/issues/277)) ([8dbca8f](https://github.com/customerio/customerio-ios/commit/8dbca8fab912913b5b26652098d5c1958dbdb4fd))
    
*   ### 2.1.0[](#210)
    
    February 22, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.0.6...2.1.0)
    
    ### Features
    
    *   add in-app event listener ([#211](https://github.com/customerio/customerio-ios/issues/211)) ([737d43b](https://github.com/customerio/customerio-ios/commit/737d43b262cb467d6fb0dfc160d558ce3de09bf7))
    *   in-app feature no longer requires orgId ([#252](https://github.com/customerio/customerio-ios/issues/252)) ([acd12da](https://github.com/customerio/customerio-ios/commit/acd12dab64e119546379f52552f2434843252682))
    
    ### Bug Fixes
    
    *   access modifier for metric ([#263](https://github.com/customerio/customerio-ios/issues/263)) ([e641982](https://github.com/customerio/customerio-ios/commit/e641982c86e5e4347977f0c0af2615d4541ff0e5))
    *   added reusable code for wrapper SDKs ([#247](https://github.com/customerio/customerio-ios/issues/247)) ([36adf15](https://github.com/customerio/customerio-ios/commit/36adf158748b7dc439812807fd4d27a287a82e1a))
    *   in-app missing event ([#259](https://github.com/customerio/customerio-ios/issues/259)) ([43b3e97](https://github.com/customerio/customerio-ios/commit/43b3e9706b152fe2dead882336a3f416ba400b73))
    *   modify in-app event listener action parameters to new name ([#255](https://github.com/customerio/customerio-ios/issues/255)) ([b46528a](https://github.com/customerio/customerio-ios/commit/b46528a0cd6cda4f8428eef0001032a79338e1cf))
    *   region visibility modifier to be used by wrappers ([#260](https://github.com/customerio/customerio-ios/issues/260)) ([f0edfbc](https://github.com/customerio/customerio-ios/commit/f0edfbc52a4694b2cee29a431d8c0943f819815b))
    *   update the gist version in podspec ([#256](https://github.com/customerio/customerio-ios/issues/256)) ([5451488](https://github.com/customerio/customerio-ios/commit/545148820187c04938e717fce4e166cd77a78f92))
    

*   ### 2.0.6[](#206)
    
    February 15, 2023[code changes](https://github.com/customerio/customerio-ios/compare/2.0.5...2.0.6)
    
    ### Bug Fixes
    
    *   universal links deep links open host app ([#268](https://github.com/customerio/customerio-ios/issues/268)) ([29c95b5](https://github.com/customerio/customerio-ios/commit/29c95b5b9810f6cdec22a15df643f8d1a9fb7d58))
    

*    [4.x Releases](#4x-releases)
    *    [4.4](#44x-releases)
        *   [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.0](#420)
    *    [4.1](#41x-releases)
        *   [4.1.2](#412)
        *   [4.1.1](#411)
        *   [4.1.0](#410)
    *    [4.0](#40x-releases)
        *   [4.0.0](#400)

*    [3.x Releases](#3x-releases)
    *    [3.14](#314x-releases)
        *   [3.14.0](#3140)
    *    [3.13](#313x-releases)
        *   [3.13.1](#3131)
        *   [3.13.0](#3130)
    *    [3.12](#312x-releases)
        *   [3.12.0](#3120)
    *    [3.11](#311x-releases)
        *   [3.11.0](#3110)
    *    [3.10](#310x-releases)
        *   [3.10.9](#3109)
        *   [3.10.8](#3108)
        *   [3.10.7](#3107)
        *   [3.10.6](#3106)
        *   [3.10.5](#3105)
        *   [3.10.4](#3104)
        *   [3.10.3](#3103)
        *   [3.10.2](#3102)
        *   [3.10.1](#3101)
        *   [3.10.0](#3100)
    *    [3.9](#39x-releases)
        *   [3.9.0](#390)
    *    [3.8](#38x-releases)
        *   [3.8.2](#382)
        *   [3.8.1](#381)
        *   [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.1](#351)
        *   [3.5.0](#350)
    *    [3.4](#34x-releases)
        *   [3.4.1](#341)
        *   [3.4.0](#340)
    *    [3.3](#33x-releases)
        *   [3.3.3](#333)
        *   [3.3.2](#332)
        *   [3.3.1](#331)
        *   [3.3.0](#330)
    *    [3.2](#32x-releases)
        *   [3.2.3](#323)
        *   [3.2.2](#322)
        *   [3.2.1](#321)
        *   [3.2.0](#320)
    *    [3.1](#31x-releases)
        *   [3.1.3](#313)
        *   [3.1.2](#312)
        *   [3.1.1](#311)
        *   [3.1.0](#310)
    *    [3.0](#30x-releases)
        *   [3.0.2](#302)
        *   [3.0.1](#301)
        *   [3.0.0](#300)

*    [2.x Releases](#2x-releases)
    *    [2.12](#212x-releases)
        *   [2.12.5](#2125)
        *   [2.12.4](#2124)
        *   [2.12.3](#2123)
        *   [2.12.2](#2122)
        *   [2.12.1](#2121)
        *   [2.12.0](#2120)
    *    [2.11](#211x-releases)
        *   [2.11.1](#2111)
        *   [2.11.0](#2110)
    *    [2.10](#210x-releases)
        *   [2.10.2](#2102)
        *   [2.10.1](#2101)
        *   [2.10.0](#2100)
    *    [2.9](#29x-releases)
        *   [2.9.2](#292)
        *   [2.9.1](#291)
        *   [2.9.0](#290)
    *    [2.8](#28x-releases)
        *   [2.8.5](#285)
        *   [2.8.4](#284)
        *   [2.8.3](#283)
        *   [2.8.2](#282)
        *   [2.8.1](#281)
        *   [2.8.0](#280)
    *    [2.7](#27x-releases)
        *   [2.7.8](#278)
        *   [2.7.7](#277)
        *   [2.7.6](#276)
        *   [2.7.5](#275)
        *   [2.7.4](#274)
        *   [2.7.3](#273)
        *   [2.7.2](#272)
        *   [2.7.1](#271)
        *   [2.7.0](#270)
    *    [2.6](#26x-releases)
        *   [2.6.1](#261)
        *   [2.6.0](#260)
    *    [2.5](#25x-releases)
        *   [2.5.3](#253)
        *   [2.5.2](#252)
        *   [2.5.1](#251)
        *   [2.5.0](#250)
    *    [2.4](#24x-releases)
        *   [2.4.1](#241)
        *   [2.4.0](#240)
    *    [2.3](#23x-releases)
        *   [2.3.0](#230)
    *    [2.2](#22x-releases)
        *   [2.2.0](#220)
    *    [2.1](#21x-releases)
        *   [2.1.2](#212)
        *   [2.1.1](#211)
        *   [2.1.0](#210)
    *    [2.0](#20x-releases)
        *   [2.0.6](#206)

---

