Manage iOS Live Activities end to end
This guide walks the full Live Activity lifecycle: hand the activity token from your iOS app to Orbit, track every running activity, push content updates, and end the activity when the tracked task completes. Follow it once and Live Activities become a closed loop — token in, updates flowing, activity ended cleanly. For request and response schemas, see the push API reference. This page covers the workflow; the push channel page covers APNs credential setup. The base push lifecycle — device tokens, targeting, and engagement — lives in the push integration guide.1. What Live Activities are
A Live Activity (iOS 16.1+, ActivityKit) is a persistent, glanceable surface on the lock screen and in the Dynamic Island. Where a normal push notification delivers a one-shot message and then decays into Notification Center, a Live Activity stays visible and its content updates in place as new pushes arrive — an order moving from Preparing to Out for delivery, a live score, a courier’s ETA. That difference drives the whole lifecycle:
Orbit tracks each activity you start so you can list, update, and end it later by its Orbit id — no need to retain the raw activity token yourself.
2. Prerequisites
- APNs credentials are set as one atomic group in your tenant’s push channel configuration — Live Activity pushes ride the same APNs transport as regular notifications. Follow the Apple push setup section of the channel page; a partial credential group (key id without team id, private key missing) fails every APNs send.
- The iOS app starts the activity with ActivityKit and obtains its APNs token for server-driven updates —
ActivityKit.Activity<T>.pushTokenfrom your app code. A device token (fromPOST /api/v1/push/device-tokens) is optional but recommended: it ties the activity to a device Orbit already knows. - Two stable identifiers decided up front: an
activity_typestring per surface (order-status,match-score,delivery-eta, …) and whether you also passcontact_idto attribute the activity to a contact for later reconciliation.
3. Start a Live Activity
The iOS app starts the ActivityKit activity locally, then your backend registers it with Orbit in one call.POST /api/v1/push/live-activities stores the activity token and fires the first start push, which tells iOS to bring the surface live:
activity_token(required) — the APNs token fromActivity.pushToken. Empty or missing values fail schema validation at 422.activity_type(required) — 1–64 chars. Pick one per surface and keep it stable; it is the fastest way to group listings later.content_state(required) — a JSON object interpreted by your app’sActivityContentshape. Serialize the same keys your ActivityKit attributes decode.device_token_id/contact_id(optional) — link the activity to a known device or contact.title,body,sound(optional) — alert content carried on the ActivityKit push;bodyaccepts up to 4,000 chars.apns_priority(optional) — one of1,5,10. Use10for time-sensitive updates, lower values when batching.relevance_score(optional) — 0–1. Higher scores promote the activity on the lock screen.
POST returns 201 with the tracked row handle:
status is the APNs send result — sent when the provider accepted the start push, failed with an error string when it did not. Hold the id; every later update and end operation addresses the activity by it.
4. Track active Live Activities
GET /api/v1/push/live-activities lists every activity this tenant has started, newest first, capped at 200 rows per page. Each row carries the full lifecycle:
statusmoves throughstarted→updated→endedas the lifecycle advances. A row stillstartednever received an update push; a rowendedis final — filter it out of any “active activities” render.activity_typeis your filter axis: scan the page for the types you care about (order-status,match-score). The endpoint reads one tenant’s rows most-recent-first, so the typical filter-and-paginate loop is client-side over pages until your predicate stops matching.ended_at/dismissal_datetell you when the surface went away (or when iOS will dismiss it); rows with neither still count as live.
5. Update a Live Activity
PUT /api/v1/push/live-activities/{id} pushes a new content_state to a running activity — iOS re-renders the surface in place:
- The path
idis the Orbit id returned at start (liveActivity_…), never the raw activity token; a malformed id fails with422 VALIDATION_ERROR. content_stateis required and replaces the previous state in full — send the complete object, not a diff.title,body,apns_priority,relevance_scoreare optional per update.- Returns
200with{ "id", "status" }— the APNs send result. Returns404 NOT_FOUNDwhen the id is unknown to this tenant or the activity has already ended; treat a 404 as “stop updating this activity” in your loop.
apns_priority to 5 when batching non-urgent transitions.
6. End a Live Activity
DELETE /api/v1/push/live-activities/{id} sends the final ActivityKit end push and closes the row:
- The body is optional.
content_stateoverrides the last stored state for the final render; omit it and the stored state is reused.dismissal_dateis Unix-epoch seconds telling iOS when to remove the activity from the lock screen; omit it and the dismissal time defaults to the moment of the call. - Returns
200with{ "id", "status" }—sentwhen theendpush was accepted,failedwith anerrorstring when APNs rejected it (the row is still markedended, so the bookkeeping closes either way). - Returns
404 NOT_FOUNDwhen the id is unknown to this tenant. Calling DELETE on an already-ended row is safe and idempotent — the row is re-stampedendedwithout re-sending the end push.
7. End-to-end example: order-status loop
A delivery backend that tracks an order from placement to handoff. The iOS app starts an ActivityKit activity on order confirmation and hands the token to your backend; the backend then owns the lifecycle:X-API-Key; the chained Idempotency-Key header dedupes retries on the start call if a timeout forces a re-POST. For error handling, the update and end calls return 404 when the activity is gone — the right signal to drop it from your tracking.
8. Deferred updates with scheduled pushes
When an update should land later — “re-check ETA at pick-up time”, “offer a wait-time recalculation” — queue it with the scheduled-push surface instead of a sleep in your backend.POST /api/v1/push/send with send_at holds the payload for the future instant; the queued row is managed (listed, inspected, cancelled) from Messages → Push → Scheduled pushes in the dashboard or via GET / DELETE /api/v1/push/scheduled. The same gates (suppression, frequency caps) replay at send time.
For the general push lifecycle this guide builds on, start with Push notifications end to end.