Live Activities

Live Activities (iOS) and Live Updates (Android) put a live tile on your customers' lock screen, such as a match score that updates as the game progresses. With one API call, you can start, update, or end the tile for every customer subscribed to a Preference Center topic, or for a single customer. Your app defines the layout; Optimove delivers the payload as-is.

How It Works

  • iOS (Live Activities, iOS 18+) — The Optimove SDK registers each device's push-to-start token with Optimove. A start creates an APNs broadcast channel and starts the activity on every device in the audience. update and end are sent once to the channel.
  • Android (Live Updates) — A start is sent to every device in the audience, and the SDK subscribes the device to an FCM topic. Update and end are sent once to the topic, and your app's handler builds the notification each time.
  • The SDKs report a started event for each device. Optimove uses these events for the audience, sent, failed, and started counts in the portal.

Prerequisites

iOS

  • Your Optimove iOS push setup uses token authentication (.p8) with your bundle ID.
  • The broadcast capability is enabled for your App ID in the Apple Developer portal.
  • A widget extension that contains your ActivityAttributes type, with NSSupportsLiveActivities set to YES. Set NSSupportsLiveActivitiesFrequentUpdates to YES for frequent updates, such as live scores.
  • Devices running iOS 18 or later.
  • Optimove iOS SDK 6.14.0 or later.

Android

  • Android push is already set up with Optimove (Firebase).
  • Notification permission is granted (Android 13+).
  • Optimove Android SDK 7.21.0 or later.

Topics

To start an activity for a topic, you need an Optimove Preference Center topic. Find your topic UUIDs with the Preference Center topics API. Find your brand UUID in Optimove under Settings → Mobile → Mobile Push Config.

Enable Live Activities in the SDKs

iOS

Define your ActivityAttributes type in code shared by your app and widget extension, then enable Live Activities when you build the Optimove config.

// Shared by the app and the widget extension
struct MatchAttributes: ActivityAttributes {
    struct ContentState: Codable, Hashable {
        var homeScore: Int
        var awayScore: Int
        var status: String
    }

    var homeTeam: String
    var awayTeam: String
    var optimoveActivityId: String? // set by Optimove on start
}

// App target only
extension MatchAttributes: OptimoveLiveActivityAttributes {}

let builder = OptimoveConfigBuilder(/* your credentials */)
if #available(iOS 18.0, *) {
    builder.enableLiveActivities(MatchAttributes.self)
}
Optimove.initialize(with: builder.build())

The SDK registers push-to-start tokens and reports started activities. Do not observe ActivityKit token updates yourself.

Android

Pass a handler to enableLiveUpdates when you build the Optimove config. The handler runs on start, on every update, and on end. LiveUpdateNotifications.post shows, updates, and removes the notification.

val config = OptimoveConfig.Builder(/* your credentials */)
    .enableLiveUpdates(LiveScoreHandler())
    .build()
Optimove.initialize(this, config)

class LiveScoreHandler : LiveUpdateHandler {
    override fun onUpdate(context: Context, event: LiveUpdateEvent, liveUpdate: LiveUpdate) {
        val state = liveUpdate.contentState
        val builder = NotificationCompat.Builder(context, "live_scores") // create this channel yourself
            .setSmallIcon(R.drawable.small_icon)
            .setContentTitle("${state.optString("homeTeam")} ${state.optInt("homeScore")} - ${state.optInt("awayScore")} ${state.optString("awayTeam")}")
            .setContentText(state.optString("status"))

        LiveUpdateNotifications.post(context, event, liveUpdate, builder)
    }
}

Control Activities with the API

Endpoint

POST https://api5.optimove.net/activities

Headers

  • X-API-KEY: Your API key. The key needs the Integrations or Admin role.
  • Content-Type: Must be application/json.

One endpoint covers the whole lifecycle. Set event to start, update, or end, and use name to identify the activity on every call.

Start an Activity

Start an activity for a topic:

curl -X POST "https://api5.optimove.net/activities" \
  -H "X-API-KEY: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "start",
    "optimove_brand_id": "<BRAND_UUID>",
    "name": "match-1234",
    "attributes_type": "MatchAttributes",
    "topic_id": "<TOPIC_UUID>",
    "attributes": {"homeTeam": "Spain", "awayTeam": "Argentina"},
    "content_state": {"homeTeam": "Spain", "awayTeam": "Argentina", "homeScore": 0, "awayScore": 0, "status": "Kick-off"},
    "alert": {"title": "Kick-off", "body": "Spain v Argentina has started"}
  }'

To start an activity for one customer, send "customer_id": "<CUSTOMER_ID>" instead of topic_id.

Update an Activity

curl -X POST "https://api5.optimove.net/activities" \
  -H "X-API-KEY: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "update",
    "optimove_brand_id": "<BRAND_UUID>",
    "name": "match-1234",
    "content_state": {"homeTeam": "Spain", "awayTeam": "Argentina", "homeScore": 1, "awayScore": 0, "status": "15'\''"},
    "alert": {"title": "Goal!", "body": "Spain 1 - 0 Argentina"}
  }'

End an Activity

curl -X POST "https://api5.optimove.net/activities" \
  -H "X-API-KEY: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "end",
    "optimove_brand_id": "<BRAND_UUID>",
    "name": "match-1234",
    "content_state": {"homeTeam": "Spain", "awayTeam": "Argentina", "homeScore": 2, "awayScore": 1, "status": "Full time"},
    "dismissal_date_s": 1793404800
  }'

Request Body Parameters

ParameterTypeMandatory/OptionalDescriptionHow it Affects Results
optimove_brand_idString (UUID)Mandatory (all events)Your brand UUID.Selects the app the activity belongs to.
eventStringMandatory (all events)start, update, or end.Determines the action taken.
nameStringMandatory (all events)Your ID for the activity. Maximum 128 characters.Identifies the activity on every call. It can't be reused, even after the activity ends.
attributes_typeStringMandatory (start)Your ActivityAttributes type name, exactly as in Swift.Tells iOS which type to decode attributes into.
topic_id or customer_idString (UUID) / StringMandatory (start) — send exactly onetopic_id is a Preference Center topic UUID. customer_id is your customer ID.Sets the audience for the start.
attributesObjectMandatory (start)The fixed fields of your ActivityAttributes.A missing or mistyped field makes iOS drop the start silently.
content_stateObjectMandatory (start, update); optional (end)The current state shown in the activity. Maximum 3 KB.Replaces the previous state.
alertObject (title, body)Mandatory (start); optional (update, end)Shown as an alert alongside the change.Without it on update or end, the change is applied without an alert.
stale_date_sInteger (Unix seconds)Optional (start, update)The time after which the content is out of date.iOS marks the content as stale after this time.
priorityStringOptional (update)high (default) or low. iOS only.high updates count toward the hourly update budget; low updates do not.
dismissal_date_sInteger (Unix seconds)Optional (end)When to remove the tile.iOS: without it the tile stays on the Lock Screen for up to 4 hours. A time that is now or in the past removes it at once.

Responses

CodeMeaning
202Accepted. Returns {"name": "..."}. The work runs in the background.
401Missing or invalid API key.
403The API key's role can't call this endpoint.
404No activity with this name, or no app for this brand.
409Name already used (start), or the activity has already ended or hasn't started yet (update, end).
422Invalid body, no device for the customer, or the platform isn't set up.

Rules and Limits

Rules

  • Send the complete content_state on every update and end. It replaces the previous state. On iOS, a push with a missing or mistyped field is dropped silently, and the same applies to attributes on start.
  • Android receives content_state and alert only, so put everything the notification shows in content_state.
  • 202 means accepted, not started. If a start fails, no activity exists, and later update and end calls return 404.
  • Use a new name for every activity.

iOS Limits

  • Starts per device — iOS limits how many Live Activities an app can start on each device in a short time, and drops starts over the limit without notice. Apple sets this limit and it can't be raised. Start only what each customer needs and don't resend failed starts.
  • Update budget — high priority updates count toward an hourly budget, and iOS may throttle updates once it runs out. low updates don't count. NSSupportsLiveActivitiesFrequentUpdates raises the budget, but users can turn frequent updates off in Settings.
  • Active at once — A device runs a limited number of Live Activities at the same time. Apple doesn't publish the number. A start fails when the device is at its limit.
  • Duration — iOS ends a Live Activity after 8 hours. It can then stay on the Lock Screen for up to 4 more hours.
  • Size — attributes and content_state together can't exceed 4 KB.
ℹ️

Apple allows 10,000 APNs broadcast channels per app, per environment. Each iOS start creates a broadcast channel (topic and customer starts create production and sandbox channels) and end deletes it, so end activities when they finish. Once the limit is reached, new iOS starts fail. APNs keeps a broadcast for up to 8 hours for devices that were offline. Started counts become final 12 hours after the end, or 20 hours after the start if the activity was never ended.

For the Mobile SDK setup and push configuration, see the Mobile guide.


Did this page help you?