> This page is part of the [Customer.io documentation](https://docs.customer.io). For the complete index, see [llms.txt](https://docs.customer.io/llms.txt).
> Last updated: August 19, 2026

# Set up live notifications

## How it works

Live notifications show information that changes over time in places your customers already look: the iOS Lock Screen and the Android notification panel. On iOS they’re built on Apple’s Live Activities; on Android, they’re built on Google’s Live Updates.

The SDK includes two built-in templates that both native SDKs render for you—a multi-step tracker and a countdown timer—so you don’t need to write a platform-specific UI for either one. You can also define a single custom type and render it yourself.

Four actors drive every live notification:

*   **You** enable the notification types your app uses, and start notifications from JavaScript when something happens in your app.
*   **The SDK** registers the push tokens each notification needs. It also reports to Customer.io when your app starts or ends a notification, and when a customer dismisses one. Updates your app makes on the device aren’t reported.
*   **Your server** starts, updates, and ends notifications by calling [the API](/integrations/api/app/#tag/live-notifications). It never talks to APNs or FCM directly.
*   **Customer.io** turns each API call into a push, using the tokens the SDK registered, and records every start, update, and end as a delivery.

iOS normally requires a fair amount of Xcode work for live notifications. The Expo config plugin does that work for you at prebuild: it adds the widget extension target, sets the `NSSupportsLiveActivities` key, adds the iOS pod subspec, and injects the `AppDelegate` code that attributes taps. Android needs no build-time setup at all.

For the concepts behind the feature and the operations your server calls, see the [live notifications overview](/messaging/channels/live-notifications/overview/).

## Prerequisites

Live notifications use the push credentials your app already has in Customer.io. Before you begin:

*   Set up [push notifications](/integrations/sdk/expo/push-notifications/push/) for your app.
*   Use **Expo SDK 53 or later**.
*   You must identify your app user before you can start a notification. The SDK holds token registrations until you identify a person, and [the API](/integrations/api/app/#tag/live-notifications/startLiveNotification) rejects requests that target anonymous profiles.
*   On iOS, target **iOS 16.2 or later**. Starting a notification from your server (push-to-start) requires iOS 17.2.
*   On Android, request `POST_NOTIFICATIONS` on Android 13 and later. To get Android 16’s promoted **Live Updates** treatment, also declare `POST_PROMOTED_NOTIFICATIONS` in your manifest. Without it, live notifications still render as ongoing notifications.

## Configure the plugin

How you turn on live notifications depends on how you initialize the SDK.

Automatic initializationManual initialization

#### Automatic initialization

Add a `liveNotifications` object under `config` in your `app.json`. Its presence enables the feature—you don’t also need `liveNotifications.enabled`.

```
{
   "expo": {
      "plugins": [
         [
            "customerio-expo-plugin",
            {
               "config": {
                  "cdpApiKey": "<CDP_API_KEY>",
                  "region": "us",
                  "liveNotifications": {
                     "types": [
                        "io.customer.livenotifications.segments",
                        "io.customer.livenotifications.countdowntimer"
                     ]
                  }
               }
            }
         ]
      ]
   }
}
```

#### Manual initialization

When you initialize the SDK from JavaScript, there’s no `config` block for the plugin to read, so turn the build-time setup on explicitly with `enabled`:

```
{
   "expo": {
      "plugins": [
         [
            "customerio-expo-plugin",
            {
               "liveNotifications": {
                  "enabled": true
               }
            }
         ]
      ]
   }
}
```

Then pass your notification types to `CustomerIO.initialize` in your app’s code, the same way you would in [React Native](/integrations/sdk/react-native/live-notifications/set-up/#initialize-the-sdk-with-live-notifications).

Run `npx expo prebuild` after you change these options, so the plugin regenerates the native projects. You only need `--clean` when you add or rename `customWidget` or `customRenderer` source files—once the widget target exists, the plugin re-syncs file contents but doesn’t discover new files.

To turn the feature off on either path, set `enabled: false` in the top-level `liveNotifications` block—it wins over everything else.

Omitting `types` enables **every** built-in template, which is what an app that picks its types at runtime needs. To register only your own custom type, set `types` to an empty array alongside `customType`. Unrecognized identifiers are ignored with a warning, so a template added in a newer SDK won’t break your build.

### Where each option goes

The plugin reads two separate `liveNotifications` blocks, and the distinction matters:

Option

Where it goes

Why

`types`, `customType`

Under `config`

Applied when the SDK initializes.

`enabled`, `branding`, `customWidget`, `customRenderer`

Top level, next to `config`

Consumed at build time. Keeping them outside `config` is what makes them work on both initialization paths.

### Branding

Branding applies to the built-in templates. The plugin compiles it into the generated iOS widget and passes it to the Android SDK, so one block covers both platforms:

```
"liveNotifications": {
   "branding": {
      "accentColorHex": "#1B5E20",
      "backgroundColorHex": "#FFFFFF",
      "textColorHex": "#1A1A1A",
      "logo": "./assets/brand-logo.png"
   }
}
```

`accentColorHex` tints the Android notification and fills the iOS progress bar. `backgroundColorHex` and `textColorHex` style the generated iOS widget only. Color values must be six-digit `#RRGGBB`—anything else fails prebuild.

`logo` takes either a project-relative path or an `http(s)` URL. The plugin copies a local path into your Android drawable resources and the iOS widget asset catalog. A URL is downloaded at render time on Android and **isn’t supported on iOS**, where the widget is compiled ahead of time.

Manual initialization needs branding in both places

If you initialize the SDK from JavaScript, set `branding` in the plugin options *and* pass it to `CustomerIO.initialize`. The plugin’s value is what it compiles into the iOS widget; the runtime value is what Android applies. On that path the plugin generates no initialization code, so it can’t forward the value for you.

## Render a custom type

To send data that Customer.io’s built-in templates don’t support by default, you can name your own type with `customType` and hand the plugin the source files that render it. You don’t need a widget target of your own—the plugin compiles your SwiftUI into the one it generates.

```
{
   "config": {
      "liveNotifications": {
         "types": ["io.customer.livenotifications.segments"],
         "customType": "com.myapp.rideshare"
      }
   },
   "liveNotifications": {
      "customWidget": {
         "sourceFile": "./ios-widgets/RideshareLiveActivity.swift",
         "structName": "RideshareLiveActivity"
      },
      "customRenderer": {
         "sourceFile": "./android-renderers/RideshareLiveNotification.kt",
         "className": "RideshareLiveNotificationCallback"
      }
   }
}
```

*   `customWidget.structName` is the `Widget` struct the plugin instantiates in the generated `WidgetBundle`. Render the SDK’s `CIOCustomAttributes` in it, and apply `.cioWidgetUrl(context.state.cioMetadata)` to your Lock Screen view and Dynamic Island regions—the plugin doesn’t wire tap attribution into your SwiftUI, so without it taps on your custom notification aren’t attributed and don’t route their deep link.
*   `customRenderer.className` is a `CustomerIOLiveNotificationsCallback` class with a no-argument constructor. It receives the same `data` map your app sends.

Configure both. Setting only one leaves the notification without a renderer on the other platform. Each option also accepts an array of paths when your renderer spans several files.

Then start it with the `Custom` template:

```
await CustomerIO.liveActivities.start({
  type: LiveActivityTemplate.Custom,
  data: { driver: 'Sam', vehicle: 'Blue Prius', minutesAway: '4' },
});
```

[GitHub project](https://github.com/customerio/customerio-expo-plugin "View the Expo SDK on GitHub")

Version

3.7.1 (Current)1.x (1.0.0-beta.17, prerelease)

©2026 Peaberry Software, Inc. [Status](https://status.customerio.com/) [Terms of Service](https://customer.io/legal/terms-of-service/) [Privacy Policy](https://customer.io/legal/privacy-policy/)

[](https://www.linkedin.com/company/customer-io)[](https://twitter.com/customerio)[](https://www.youtube.com/channel/UCkCaWdezRoa8ZyR9pEVaipA)[](https://www.instagram.com/customer.io/)
