Skip to main content

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>.pushToken from your app code. A device token (from POST /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_type string per surface (order-status, match-score, delivery-eta, …) and whether you also pass contact_id to 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 from Activity.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’s ActivityContent shape. 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; body accepts up to 4,000 chars.
  • apns_priority (optional) — one of 1, 5, 10. Use 10 for 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:
  • status moves through startedupdatedended as the lifecycle advances. A row still started never received an update push; a row ended is final — filter it out of any “active activities” render.
  • activity_type is 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_date tell you when the surface went away (or when iOS will dismiss it); rows with neither still count as live.
The dashboard renders the same list under Messages → Push → Live Activities, so operations and engineering read one source of truth.

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 id is the Orbit id returned at start (liveActivity_…), never the raw activity token; a malformed id fails with 422 VALIDATION_ERROR.
  • content_state is required and replaces the previous state in full — send the complete object, not a diff. title, body, apns_priority, relevance_score are optional per update.
  • Returns 200 with { "id", "status" } — the APNs send result. Returns 404 NOT_FOUND when the id is unknown to this tenant or the activity has already ended; treat a 404 as “stop updating this activity” in your loop.
Update pacing is on you: frequent, small updates burn APNs throughput and battery, so advance the state only when it materially changed, and drop 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_state overrides the last stored state for the final render; omit it and the stored state is reused. dismissal_date is 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 200 with { "id", "status" }sent when the end push was accepted, failed with an error string when APNs rejected it (the row is still marked ended, so the bookkeeping closes either way).
  • Returns 404 NOT_FOUND when the id is unknown to this tenant. Calling DELETE on an already-ended row is safe and idempotent — the row is re-stamped ended without re-sending the end push.
End every activity you start. Orphaned activities linger on the user’s lock screen until iOS expires them, which reads as a bug in your app.

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:
Every call carries 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.