> This page is part of the [Customer.io documentation](https://docs-customerio.netlify.app). For the complete index, see [llms.txt](https://docs-customerio.netlify.app/llms.txt).
> Last updated: July 30, 2026

# Live Activities

[PremiumThis feature is available for Premium plans.](/accounts/billing/plan-features/) [EnterpriseThis feature is available for Enterprise plans.](/accounts/billing/plan-features/)

Live Activities let you push real-time, continuously updated notifications to the iOS Lock Screen and Dynamic Island, like a delivery tracker, a live score, or a countdown. Define your activity’s data model, render it with your own widget, and let Customer.io push updates without your app being open.

See [How live notifications work](/messaging/channels/live-notifications/how-it-works/) to learn more about the live notification lifecycle.

 Requirements

Live Activities require iOS 16.2 or later—iOS 17.2 or later for push-to-start (starting activities from your server). Your app must identify the person before the SDK registers tokens. Live Activities are available on the native iOS SDK installed with either Swift Package Manager or CocoaPods.

## How the pieces fit

*   **You** define an ActivityKit `ActivityAttributes` type describing your activity’s data, and a widget that renders it. The SDK never renders your activity—the design is entirely yours.
*   **The SDK** observes activities of the types you register, captures and registers push tokens, and reports lifecycle events to Customer.io.
*   **Your server** starts, updates, and ends activities by calling [the API](/integrations/api/app/#tag/live-notifications)—it never talks to APNs.

## Before you begin

You need to complete two one-time setup steps in Xcode:

1.  **Add a Widget Extension target** (**File** > **New** > **Target** > **Widget Extension**) if you don’t already have one. Your Live Activity UI lives here.
2.  **Enable Live Activities** by setting `NSSupportsLiveActivities` to `YES` in your app target’s Info settings. If your activity updates frequently, also set `NSSupportsLiveActivitiesFrequentUpdates` to `YES`.

## Step 1: Add the SDK packages

Live Activities span two libraries. Add each to the correct target, using the names for your dependency manager:

Installation

App target

Widget extension

Swift Package Manager

`LiveActivities`

`LiveActivities_Attributes`

CocoaPods

`CustomerIOLiveActivities`

`CustomerIOLiveActivitiesAttributes`

The attributes library (`LiveActivities_Attributes` or `CustomerIOLiveActivitiesAttributes`) contains only protocols and value types—no SDK machinery—so it’s safe to import in your widget extension, which needs it to share your activity’s data model with the app. Add it to **both** the app target and the widget extension target.

If your app uses the bundled templates, also add the templates library to both targets—`LiveActivities_Templates` with Swift Package Manager, or `CustomerIOLiveActivitiesTemplates` with CocoaPods.

## Step 2: Define your activity’s data model

Create an `ActivityAttributes` type describing your activity. Make the file a member of **both** your app target and your widget extension target—ActivityKit matches the two sides by the type’s name and `Codable` shape, so sharing one source file is the intended pattern.

```swift
import ActivityKit
import CioLiveActivities_Attributes

struct DeliveryActivityAttributes: CIOActivityAttribute {
    // Reverse-DNS identifier, registered with the SDK and matched
    // server-side to route pushes.
    static let identifier = "io.customer.liveactivities.deliverytracking"

    // Managed by Customer.io. Declare it with a default and never set it
    // yourself—the SDK fills it in on local start, and Customer.io fills
    // it in for push-to-start.
    var cioInstanceId: String = ""

    // Your static fields—set once at start, never change.
    var orderNumber: String

    // Your dynamic fields—updated over the activity's life.
    struct ContentState: Codable, Hashable, CIOMetadataCarrying {
        var title: String
        var subtitle: String?
        var estimatedArrival: EpochSecondsDate?

        // Deep-link and delivery metadata for the push that produced
        // this state. nil for locally-driven updates.
        var cioMetadata: CIOLiveActivityMetadata?
    }
}
```

Three Customer.io-specific pieces to note:

*   `CIOActivityAttribute` is a one-property protocol (`cioInstanceId`) that opts your activity type into **push-to-start**, so your server can create the activity remotely. A plain `ActivityAttributes` type also works. The SDK still observes it for local starts, updates, and ends, but you can only start conforming activity types from your server.
*   `CIOMetadataCarrying` is optional. Declare `cioMetadata` on your `ContentState` to receive each update’s deep link and delivery attribution.
*   `EpochSecondsDate` decodes the epoch-second date fields Customer.io sends, so live countdowns work without custom decoding.

## Step 3: Build your widget

Render your activity in your widget extension with a standard ActivityKit `ActivityConfiguration`. This is your SwiftUI—Customer.io doesn’t constrain the design.

```swift
import ActivityKit
import CioLiveActivities_Attributes
import SwiftUI
import WidgetKit

struct DeliveryActivityWidget: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: DeliveryActivityAttributes.self) { context in
            // Your Lock Screen view
            DeliveryLockScreenView(state: context.state)
                .cioWidgetUrl(context.state.cioMetadata)
        } dynamicIsland: { context in
            DynamicIsland {
                DynamicIslandExpandedRegion(.bottom) {
                    Text(context.state.title)
                }
            } compactLeading: {
                Image(systemName: "shippingbox.fill")
            } compactTrailing: {
                Text(context.state.subtitle ?? "")
            } minimal: {
                Image(systemName: "shippingbox.fill")
            }
        }
    }
}
```

Use `.cioWidgetUrl(context.state.cioMetadata)` so a tap opens the deep link your server attached to the latest push and is attributed to the exact delivery. Add the widget to your extension’s `WidgetBundle`.

## Step 4: Initialize the module

Add the Live Activities module to your SDK configuration and register each activity type you want Customer.io to observe. The module is registered with the main SDK, so there’s no separate instance to hold onto—you drive activities through `CustomerIO.liveActivities`.

```swift
import CioDataPipelines
import CioLiveActivities

let configBuilder = SDKConfigBuilder(cdpApiKey: "YOUR_CDP_API_KEY")

if #available(iOS 16.2, *) {
    configBuilder.addModule(
        LiveActivitiesModule(
            config: LiveActivityConfigBuilder()
                .register(
                    DeliveryActivityAttributes.self,
                    identifier: DeliveryActivityAttributes.identifier
                )
                .build()
        )
    )
}

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

The `register` call tells the SDK which activity type to watch. From here on, any activity of that type is observed automatically. The SDK registers its tokens with Customer.io and reports its lifecycle. There’s no per-activity code.

## Step 5: Start an activity

There are two ways to start a live activity:

*   **From your server (push-to-start):** You call [the API](/integrations/api/app/#tag/live-notifications/startLiveNotification) when you’re ready to start an activity and iOS creates it on the device. Your app doesn’t need to be open. This is the most common path for activities like delivery tracking and live scores, and requires iOS 17.2.
*   **From your app:** Start the activity through the module when something happens in the app. This is a common path for activities like a workout progress tracker.

### From your server

Call the [start endpoint](/integrations/api/app/#tag/live-notifications/startLiveNotification) to start an activity for a person. Customer.io sends the push-to-start message, and iOS creates the activity with the attributes and content state from your payload.

### From your app

Start the activity through the module—when a customer places an order, for example:

```swift
let activity = try CustomerIO.liveActivities.start(
    DeliveryActivityAttributes(orderNumber: "CIO-1234"),
    contentState: .init(title: "Order received")
)
```

`start` returns a `CIOLiveActivity` handle. Calling `activity.update(_:)` changes the Live Activity on the device but isn’t reported to Customer.io. Calling `activity.end(_:)` ends it locally and reports the end. If your app already starts activities with raw ActivityKit calls, pass them to `CustomerIO.liveActivities.adopt(_:)` so the SDK can manage future updates and the end—but note that `adopt` doesn’t report that activity’s original start.

Once the activity is running, your server can push subsequent updates by calling the [update endpoint](/integrations/api/app/#tag/live-notifications/updateLiveNotification) with the activity’s instance ID. You don’t need to manage push tokens yourself.

## Handle deep links

When a customer taps an activity, and the latest push carried a deep link, iOS opens your app with the widget URL. Pass it through `CustomerIO.liveActivities.handleWidgetUrl(_:)` before your own routing so the tap is attributed to the exact delivery:

```swift
func application(_ app: UIApplication, open url: URL,
                 options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    guard let destination = CustomerIO.liveActivities.handleWidgetUrl(url) else {
        return true
    }
    return handleDeepLink(destination)
}
```

`handleWidgetUrl` records the tap for metrics. For non-Customer.io URLs, it returns the original URL unchanged. For a Customer.io widget URL, it returns the embedded destination, or nil if no destination was provided.

## Related pages

*   [Live notifications overview](/messaging/channels/live-notifications/overview/)
*   [How live notifications work](/messaging/channels/live-notifications/how-it-works/)
*   [Payload reference](/messaging/channels/live-notifications/payload-reference/)

Version[](https://github.com/customerio/customerio-ios/releases/tag/4.7.0 "See this release in GitHub")

4.7.0 (Current)3.14.02.14.2

*   *   [How the pieces fit](#how-the-pieces-fit)
    *   [Before you begin](#before-you-begin)
    *   [Step 1: Add the SDK packages](#step-1-add-the-sdk-packages)
    *   [Step 2: Define your activity’s data model](#step-2-define-your-activitys-data-model)
    *   [Step 3: Build your widget](#step-3-build-your-widget)
    *   [Step 4: Initialize the module](#step-4-initialize-the-module)
    *   [Step 5: Start an activity](#step-5-start-an-activity)
        *   [From your server](#from-your-server)
        *   [From your app](#from-your-app)
    *   [Handle deep links](#handle-deep-links)
    *   [Related pages](#related-pages)

Copy page

Copy page [Download .md](/integrations/sdk/ios/push/live-activities.md) [Download iOS SDK bundle](/files/ios-sdk-docs.md)

Is this page helpful?

![](https://docs.customer.io/images/export-success.png) ![](https://docs.customer.io/images/export-failure.png)

# How can we make it better?

Close

Do you need help from Customer.io support?  No  
 Yes

What part of Customer.io do you need help with? 

How can we improve this page?

Email (optional):  Please provide a valid email address

 I am not a bot

 

We appreciate your feedback!

Our support team will contact you as soon as possible
