Live Activities

Teak can schedule server-driven updates to iOS Live Activities your app starts itself, and attribute taps on those activities back to the Teak campaign that drove them.

Live Activity APIs require Teak SDK 4.3.12 or newer, and iOS 16.2 or newer on the device. Guard usage with #if UNITY_IOS && TEAK_4_3_OR_NEWER at compile time and check the runtime OS before calling.
Teak drives updates for Live Activities your app develops and starts itself. Starting an activity from a push-to-start payload will come in a later release.

What are Live Activities?

A Live Activity is a small, live-updating view iOS shows outside your app, on the Lock Screen and in the Dynamic Island, while an event is happening. It shows a few values that change over a set period and then ends. See Apple’s ActivityKit documentation for the presentation contexts, sizes, and lifecycle.

Live Activities fit a single ongoing event with a real beginning and end, ideally one the player is actively taking part in rather than just something happening in the background. Think a tournament they’ve entered, a chest or energy timer they triggered, a limited-time event they’ve joined, or a boost they activated.

Apple’s guidelines say to avoid ads and promotions in a Live Activity, and App Store Review Guideline 4.5.3 bars using them to spam or send unsolicited messages. Treat them as genuine status, not a promotional channel, broad re-engagement tool, or permanent dashboard.

Read Apple’s Human Interface Guidelines for Live Activities and design your Live Activity within Apple’s rules before you build it. This page covers the Teak integration once it fits those rules.

Integration overview

Unity does not expose ActivityKit directly, so the integration has two halves:

  1. A Swift bridge in your Xcode project that owns the ActivityAttributes type, starts the Activity<T>, and observes activity.pushTokenUpdates. You forward each token value and the activity.id back into Unity — via UnitySendMessage, a C# callback, or your preferred interop pattern.

  2. C# calls into Teak.LiveActivity from your Unity code: report the received token with StartedLiveActivity, schedule updates with ScheduleLiveActivityUpdate, and cancel pending updates with CancelLiveActivityUpdates.

The widget extension that provides the Live Activity UI is a standard Xcode target — see Apple’s Live Activities documentation for the layout, ActivityConfiguration, and widget-bundle basics.

What Teak needs from you

Three values, all from the Live Activity APIs Apple already gives you:

  • Push-to-update token — one per instance, issued when the activity starts. Observe activity.pushTokenUpdates in Swift for each activity you start and forward every value into Unity.

  • System activity idactivity.id, the per-instance identifier iOS assigns. Pass it alongside the push-to-update token so Teak can attribute taps back to the right instance.

  • Your activityId string — a stable game-chosen string (for example, "chest_timer") that Teak uses as the schedule key. It must be unique per concurrent instance — if a player can have three chest timers running at once, give them distinct ids like "chest_timer_0", "chest_timer_1", "chest_timer_2". Reusing the same activityId across two live instances will collide on the server side. Each instance has its own push token, and Teak keys both scheduling and cancellation off your activityId, so distinct ids are what let it target and cancel the right instance when several run at once.

Report the push-to-update token

Once your Swift bridge has received the token bytes and the system activity id, hand them to Teak from C#:

#if UNITY_IOS && TEAK_4_3_OR_NEWER
// From your bridge callback: pushToken is a byte[], systemActivityId is a string.
StartCoroutine(Teak.LiveActivity.StartedLiveActivity(
    "chest_timer",
    pushToken,
    systemActivityId,
    (Teak.LiveActivity.Reply reply) => {
        if (reply.Error) {
            Debug.LogWarning("Teak LiveActivity registration failed: " + reply);
        }
    }));
#endif

An overload accepting a hex-encoded token string is available for callers that already hold the token as a string (for example, persisted via PlayerPrefs):

StartCoroutine(Teak.LiveActivity.StartedLiveActivity(
    "chest_timer",
    pushTokenHex,           // lowercase hex, even length
    systemActivityId,
    reply => { /* ... */ }));

Call this every time pushTokenUpdates yields a new value. Tokens rotate.

Schedule an update

You don’t need a scheduled update for every tick of a countdown. Render elapsing time with Text(timerInterval:) and ProgressView(timerInterval:), which update on-device with no push, and reserve Teak-scheduled updates for genuine data changes such as a status flip, a new rank, or "reward ready." See Displaying live data with Live Activities.
#if UNITY_IOS && TEAK_4_3_OR_NEWER
Dictionary<string, object> customData = new Dictionary<string, object> {
    { "remaining", 0 },
    { "status", "ready" }
};
Dictionary<string, object> systemData = new Dictionary<string, object> {
    { "event", "update" }
};
StartCoroutine(Teak.LiveActivity.ScheduleLiveActivityUpdate(
    "chest_timer",
    60,                     // offset in seconds from server-now
    customData,
    systemData,             // optional — pass null if you don't need system fields
    reply => {
        if (reply.Error) {
            Debug.LogWarning("Schedule failed: " + reply);
        }
    }));
#endif
  • offset is seconds from server-now. The server resolves the absolute delivery time, so device clock skew doesn’t matter.

  • customData becomes the APNs content-state. Values must be JSON-serializable; pre-encode any dates per Apple’s content-state conventions. It is required — pass a non-null dictionary.

  • systemData is optional and carries Apple system fields (event, stale-date, dismissal-date). Pass null if you don’t need it. Use these to drive the activity’s lifecycle: stale-date marks when the content should be treated as outdated (the activity’s state flips to stale and the system flags it, so your view can refresh), and dismissal-date schedules its removal. A Live Activity is meant to represent a task with a real end, so set them on the update that finishes the mechanic.

Keep the payload lean. Apple caps the entire push at 4 KB (the content-state plus the rest of the JSON), so send only the values your UI renders, not general game state. The activity’s view also has no network access, so bundle the images it shows or share them through an App Group rather than passing URLs.

Rate limits on updates

Apple budgets high-priority Live Activity updates based on device conditions and throttles or drops them once that budget is spent; the budget only replenishes over time. Low-priority updates aren’t count-capped but may be delayed. Apple doesn’t publish a fixed rate, so schedule meaningful changes rather than a constant stream.

If a mechanic genuinely needs frequent updates, set the NSSupportsLiveActivitiesFrequentUpdates Info.plist key in your app; it raises the budget but doesn’t guarantee delivery. These are Apple’s limits and can change. See Apple’s push-update guidance.

Cancel pending updates

#if UNITY_IOS && TEAK_4_3_OR_NEWER
StartCoroutine(Teak.LiveActivity.CancelLiveActivityUpdates(
    "chest_timer",
    reply => {
        if (!reply.Error && reply.CanceledCount.HasValue) {
            Debug.Log("Canceled " + reply.CanceledCount.Value + " pending update(s).");
        }
    }));
#endif

Cancellation is scoped to the current user and the given activityId. The reply’s CanceledCount carries the number of updates the server dropped.

Because activityId is the cancellation scope, this is another reason to give concurrent instances distinct ids — cancelling "chest_timer_0" won’t touch pending updates for "chest_timer_1".

Attribute taps

When a player taps a Live Activity and resumes your app, Teak routes the launch through its normal attribution pipeline. Observe Teak.OnPostLaunchSummary as usual — for a Live Activity tap, TeakPostLaunchSummary carries:

  • SystemActivityId — the activity.id of the tapped activity

  • ScheduleId and ScheduleName — the Teak schedule that drove the activity, once the session’s server response enriches the launch

Teak.Instance.OnPostLaunchSummary += summary => {
    if (!string.IsNullOrEmpty(summary.SystemActivityId)) {
        Debug.Log("Launched from Live Activity " + summary.SystemActivityId +
                  " (schedule " + summary.ScheduleName + ")");
    }
};

No extra wiring required beyond an observer for OnPostLaunchSummary.

Live Activity taps currently surface only the schedule id and name. Creative and reward attribution is available for push and deep-link launches but is not yet sent down for Live Activity clicks.