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
startcreates an APNs broadcast channel and starts the activity on every device in the audience.updateandendare sent once to the channel. - Android (Live Updates) — A
startis sent to every device in the audience, and the SDK subscribes the device to an FCM topic.Updateandendare sent once to the topic, and your app's handler builds the notification each time. - The SDKs report a
startedevent 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
ActivityAttributestype, withNSSupportsLiveActivitiesset toYES. SetNSSupportsLiveActivitiesFrequentUpdatestoYESfor 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 beapplication/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
| Parameter | Type | Mandatory/Optional | Description | How it Affects Results |
|---|---|---|---|---|
optimove_brand_id | String (UUID) | Mandatory (all events) | Your brand UUID. | Selects the app the activity belongs to. |
event | String | Mandatory (all events) | start, update, or end. | Determines the action taken. |
name | String | Mandatory (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_type | String | Mandatory (start) | Your ActivityAttributes type name, exactly as in Swift. | Tells iOS which type to decode attributes into. |
topic_id or customer_id | String (UUID) / String | Mandatory (start) — send exactly one | topic_id is a Preference Center topic UUID. customer_id is your customer ID. | Sets the audience for the start. |
attributes | Object | Mandatory (start) | The fixed fields of your ActivityAttributes. | A missing or mistyped field makes iOS drop the start silently. |
content_state | Object | Mandatory (start, update); optional (end) | The current state shown in the activity. Maximum 3 KB. | Replaces the previous state. |
alert | Object (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_s | Integer (Unix seconds) | Optional (start, update) | The time after which the content is out of date. | iOS marks the content as stale after this time. |
priority | String | Optional (update) | high (default) or low. iOS only. | high updates count toward the hourly update budget; low updates do not. |
dismissal_date_s | Integer (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
| Code | Meaning |
|---|---|
202 | Accepted. Returns {"name": "..."}. The work runs in the background. |
401 | Missing or invalid API key. |
403 | The API key's role can't call this endpoint. |
404 | No activity with this name, or no app for this brand. |
409 | Name already used (start), or the activity has already ended or hasn't started yet (update, end). |
422 | Invalid body, no device for the customer, or the platform isn't set up. |
Rules and Limits
Rules
- Send the complete
content_stateon 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 toattributeson start. - Android receives
content_stateandalertonly, so put everything the notification shows incontent_state. 202means accepted, not started. If a start fails, no activity exists, and laterupdateandendcalls return404.- Use a new
namefor 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 —
highpriority updates count toward an hourly budget, and iOS may throttle updates once it runs out.lowupdates don't count.NSSupportsLiveActivitiesFrequentUpdatesraises 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 —
attributesandcontent_statetogether 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
enddeletes 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.
Updated about 1 hour ago
