Skip to main content
Build Live Updates on Android by sending a structured payload over OneSignal push and updating a single notification in place from your NotificationServiceExtension. Use it for ongoing, user-initiated activities such as a delivery in progress, a live match score, a ride status, or a file upload.
Live Updates are Android’s version of iOS Live Activities. Android documents them as Live Updates, while the underlying APIs call them promoted ongoing notifications.Unlike iOS Live Activities, this is a pattern you implement in your app rather than a managed OneSignal feature.

When to use Live Updates

Send a Live Update when the user needs to glance at changing information without opening anything. On Android 16 QPR1 and higher, a promoted Live Update sits at the top of the notification drawer, takes prominent placement on the lock screen, and appears as a chip in the status bar while the shade is closed. Samsung routes Live Updates into the Now Bar, which also surfaces on the always-on display. That persistent, glanceable presence is the whole benefit. An ordinary ongoing notification sits in the shade and stays invisible until the user pulls it down. Android defines three criteria for the pattern. A Live Update should represent an activity that is ongoing (actively in progress, with a distinct start and end), user-initiated (the user explicitly started it), and time-sensitive (worth their attention for the whole duration). See Google’s usage criteria for the full guidance. Good fits. Active navigation, an ongoing phone call, a ride in progress, a food delivery, a workout, a file upload, or a live match the user chose to follow. For an activity the user opted into rather than started, such as a game they asked to track, include an unpin action so they can stop following it. Poor fits. Anything the user didn’t start, anything without an end state, and anything you would send to a segment. Google names ads, promotions, chat messages, alerts, upcoming calendar events, and shortcuts to app features as inappropriate uses. Something that already finished belongs in a standard notification. What it costs you. An extra permission, a set of eligibility rules every notification must satisfy, and a version branch in your extension. Promotion only appears on Android 16 QPR1 and higher, so expect most of your audience to see the unpromoted rendering for now.
Users can demote a Live Update back to a standard notification, and they can turn promotion off for your app entirely. Promoting content that isn’t an ongoing, user-initiated activity is a fast way to lose the surface, or to lose notification permission altogether.
If your content doesn’t need to be glanceable, such as a single completion notice or an alert the user reads once, send a standard push instead.

Requirements

  • OneSignal Android SDK 5.1.14 or newer integrated in your app. Version 5.1.14 added the preventDefault(discard) overload this pattern relies on.
  • The end user has granted push notification permission. Live Updates are delivered over push, so a user who has not granted permission never receives them.
  • A NotificationServiceExtension in your app to intercept and render Live Updates. See Service extensions for background.
  • Your app built against compileSdk 36 or higher. This is required to compile Notification.ProgressStyle and setShortCriticalText.
  • A device running Android 16 QPR1 (API 36.1) or higher for the promoted “Live Update” treatment: prominent placement plus a status chip when you set short critical text. On Android 16.0 and lower the notification still updates in place, just without promotion.
Two API levels are involved. Notification.ProgressStyle, setShortCriticalText, hasPromotableCharacteristics(), and canPostPromotedNotifications() were added in API 36. The promotion setter setRequestPromotedOngoing and the EXTRA_REQUEST_PROMOTED_ONGOING constant were added in API 36.1, so at compileSdk 36 you request promotion with the extra’s string key instead. Step 1 explains how the sample code handles this.

How it works

OneSignal is the transport. Your app code defines the schema and renders the UI. The flow:
  1. Your backend calls the Create message API with a collapse_id and a structured live_notification object inside data.
  2. OneSignal delivers the push to the device.
  3. Your NotificationServiceExtension intercepts every push, looks for the live_notification key, and dispatches based on its event (start, update, or end).
  4. Your code calls NotificationManager.notify(id, …) with the same Android notification ID each time, which updates the existing notification rather than posting a new one.
The live_notification payload, its field names, and the start / update / end event values are a convention introduced by this guide. They are not enforced by OneSignal. You can rename or restructure anything as long as your service extension and your sending backend agree.

Live Updates vs. standard push

Unlike regular push notifications, which post a new notification each time, Live Updates use one persistent notification updated over time. Two things make this work:
  • A stable Android notification ID on the device side lets NotificationManager.notify(id, …) replace the visible notification’s contents instead of stacking a new one. This is what produces the single updating notification.
  • collapse_id on the API side ties every message to the same logical notification so a device that was offline receives only the newest update instead of the whole backlog. See Collapse ID (mobile push).

Implementation

1. Add a Notification Service Extension

Create a class that implements INotificationServiceExtension. Place it at app/src/main/java/<your-package-path>/NotificationServiceExtension.kt, where <your-package-path> matches your app’s package (for example, com/yourcompany/yourapp/). The class below is complete and runnable. It creates the notification channel on first use, parses the live_notification payload, dispatches by event, honors dismissals, and renders a progress notification. Replace com.onesignal.sample.android in the package declaration with your app’s package name (for example, com.yourcompany.yourapp); the same fully qualified name must appear in the Manifest entry in Step 2. The @Keep annotation is required to stop R8 or ProGuard from stripping or renaming the class when minification is enabled. See Service extensions for more on writing service extensions. Two details are worth noticing before you read it. The sample branches on the OS version, so devices on API 36 and above get the promoted Live Update treatment while everything below that gets an ordinary ongoing progress notification. Keep both paths unless your minSdk is 36 or higher, because the Live Update APIs do not exist on older devices and calling them there throws at runtime. The sample also records when a user dismisses a Live Update and stops updating it, which Android requires and which this push-driven pattern would otherwise violate on the next update.
NotificationServiceExtension.kt
Channels for Live Updates should be low importance with badges, vibration, and sound disabled so frequent updates don’t alert the user. See Android notification categories.
The extension references two small helpers. Add both files next to it, in the same package. LiveUpdateState records dismissals in SharedPreferences because the extension keeps no memory between pushes. LiveUpdateDismissReceiver catches the swipe or the Unpin tap and writes that record.
Without this dismissal record, your next update push reposts a notification the user just swiped away, and it returns every few seconds until the activity ends. Android names that behavior as a reason users revoke promotion permission for an app.
The promoted path uses the framework Notification.Builder, and the pre-API-36 path uses NotificationCompat.Builder. This split is deliberate.NotificationCompat.ProgressStyle and NotificationCompat.Builder.setRequestPromotedOngoing were both added in androidx.core 1.17.0, which the OneSignal SDK does not pull in. Building the promoted notification with the framework class avoids forcing a dependency bump on every integrator.The tradeoff is the promotion flag. Notification.Builder.setRequestPromotedOngoing and Notification.EXTRA_REQUEST_PROMOTED_ONGOING were added in API 36.1, so neither is available at compileSdk 36. Setting the android.requestPromotedOngoing extra by its string key produces the same result. If you already depend on androidx.core 1.17.0 or newer, use NotificationCompat.Builder with setRequestPromotedOngoing(true) for both paths and drop the string literal.

2. Register the extension in the Android Manifest

Open app/src/main/AndroidManifest.xml and add three entries. Do not create a new file, and do not add a <service> tag. The <meta-data> entry alone tells OneSignal which class from Step 1 to call when a push arrives.
  • Add <uses-permission> at the top level of <manifest>, alongside your other permissions and outside <application>. POST_PROMOTED_NOTIFICATIONS is a non-runtime permission, so declaring it is all you need. There is no prompt to request.
  • Add <meta-data> inside the existing <application> block. Leave android:name exactly as shown, and set android:value to the fully qualified name of your class (the package declaration in NotificationServiceExtension.kt plus the class name).
  • Add <receiver> inside <application> so the dismissal handler from Step 1 can run. Keep android:exported="false", since only the system needs to deliver to it.
Treat the second tab as a placement reference, not a replacement for your existing Manifest.
Steps 1 and 2 are the only required Android file changes. The remaining steps cover payload format and how to send Live Updates from your backend.

3. Define your Live Update payload

Each Live Update carries a live_notification object inside the push’s data field. The key name and the schema are conventions you control, and this guide keeps live_notification so existing implementations keep working. The shape used here: Example payload (start of a download progress Live Update):
Two constraints shape what you put in here:
  • key must map to an Android notification ID your extension already knows. The sample in Step 1 registers progress in keyMap. A payload with an unregistered key logs a warning and renders nothing, so add an entry to keyMap and a builder for every key you send.
  • Resend event_attributes on every event, not only on start. The service extension only runs when a push arrives and keeps no state between pushes, so anything it read on start is gone by the time the first update arrives.
FCM caps the total push payload at 4 KB. Keep event_attributes and event_updates to the fields your builder actually reads, and send IDs rather than full objects your app can already resolve locally.

4. Handle the lifecycle events

Your service extension dispatches on the event field. The example code above does this in handleLiveUpdate. The sample also treats dismissal as part of the lifecycle. start clears any dismissal recorded for that key, update is skipped while a dismissal is on record, and end cancels the notification and clears the record so the next activity starts clean.
Always reuse the same Android notification ID for a given key across start, update, and end. Using a new ID posts a separate notification instead of updating the existing one.

Confirm a Live Update will promote

The manifest permission is only half of it. Two independent checks tell you why a notification isn’t promoting, and they fail for different reasons:
  • Notification.hasPromotableCharacteristics() returns false when the notification itself doesn’t qualify. It must be ongoing, have a contentTitle, and use Standard, BigText, Call, Progress, or Metric style. It must not set colorized to true, set a customContentView, be a group summary, or post to an IMPORTANCE_MIN channel. This check ignores user settings.
  • NotificationManager.canPostPromotedNotifications() returns false when the user has turned promotion off for your app.
To check the notification itself, call hasPromotableCharacteristics() on the built notification inside your extension and log the result. If it returns false, fix the builder against the list above. To check the user setting, deep-link the user to the promotion screen. Call this from your app UI, not from the service extension, since the extension has no Activity to start from:
Both canPostPromotedNotifications() and ACTION_APP_NOTIFICATION_PROMOTION_SETTINGS were added in API 36.
Some Android documentation refers to this intent action as Settings.ACTION_MANAGE_APP_PROMOTED_NOTIFICATIONS. That constant does not exist. Use Settings.ACTION_APP_NOTIFICATION_PROMOTION_SETTINGS.
Without these checks, “the user disabled Live Updates” and “the notification is malformed” look identical from your side. Both produce a notification that updates in place but never promotes, with nothing in the logs to tell them apart.
Passing both checks still doesn’t guarantee promotion. Device manufacturers can add their own criteria for Live Updates, so a notification that promotes on a Pixel may not promote on every device.

Verify you’re ready to test

Before sending curl requests, confirm the following:
  • The device shows as subscribed in the OneSignal dashboard.
  • Push permission is granted on the device.
  • You have a Subscription ID or External ID for the target user.
  • You have your app’s REST API key from Settings → Keys & IDs.
The examples below target include_aliases.external_id, which only works if your app has called OneSignal.login("EXTERNAL_ID") for that user. If login was never called, the send fails and no notification arrives. See External ID. If you are not sure, swap the targeting block for a Subscription ID from the dashboard, which always resolves:
include_subscription_ids replaces both include_aliases and target_channel, so remove those two fields when you use it.

Send a Live Update

Send start, update, and end events with the Create message API. Every request must:
  • Set the same collapse_id on every event so an offline device receives only the newest update. The ID must be unique to that specific Live Update instance.
  • Target the same set of users (typically with include_subscription_ids or include_aliases.external_id).
  • Use isAndroid: true to restrict delivery to Android push subscriptions.
Set a short ttl on update pushes, in seconds, roughly matching how long that value stays meaningful. Without it an update queued for an offline device can arrive long after the activity finished and repost a notification for something that’s already over. Leave end at the default so it still lands after a gap in connectivity.

Start

Send event: "start" to create the Live Update. Initialize both static (event_attributes) and dynamic (event_updates) data.
Start

Update

Send event: "update" with new event_updates data. You can update as many times as you like after start. Repeat event_attributes in each update if your builder reads it, since the extension retains nothing from the start push.
Update

End

Send event: "end" to dismiss the Live Update. The service extension calls NotificationManager.cancel(id) to remove it from the user’s device.
End

FAQ

Are Android Live Updates a managed OneSignal feature?

No. They are a pattern you implement in your NotificationServiceExtension on top of OneSignal push and Android’s NotificationManager. OneSignal does not track Live Update state, manage update lifecycles, or expose Live Updates in the dashboard. The schema and event names in this guide are conventions you can change.

Why isn’t my Live Update promoting?

Check the two independent gates. Notification.hasPromotableCharacteristics() is false when the notification itself doesn’t qualify: missing content title, ineligible style, colorized, or an IMPORTANCE_MIN channel. NotificationManager.canPostPromotedNotifications() is false when the user disabled Live Updates for your app, so deep-link them to Settings.ACTION_APP_NOTIFICATION_PROMOTION_SETTINGS to re-enable it. Also confirm the device is on Android 16 QPR1 (API 36.1) or higher, because Android 16.0 renders the notification and updates it in place but never promotes it. See Confirm a Live Update will promote.

Why doesn’t my Live Update appear in the Now Bar on my Galaxy device?

Samsung integrates Android’s Live Updates into the Now Bar, but some One UI builds have gated third-party Live Updates behind a developer setting named Live notifications for all apps, leaving the Now Bar to Samsung’s own apps by default. If your notification promotes correctly elsewhere on the device (status bar chip, lock screen, top of the drawer) but never reaches the Now Bar, check that setting on the test device before changing your code. Device manufacturers can also add their own eligibility criteria, so confirm behavior on the specific devices your audience uses rather than on one reference phone.

Why do I need both collapse_id and a stable Android notification ID?

They solve different problems, and only the notification ID controls what the user sees. The stable Android notification ID is what NotificationManager.notify(id, …) uses to replace the visible notification’s contents instead of posting a new one. collapse_id maps to the FCM collapse_key, which discards superseded messages queued for a device that is currently offline, so a device that reconnects after ten updates receives the last one rather than all ten. Set both: the ID for correct rendering, the collapse_id to avoid a flood of stale updates on reconnect.

How often can I send updates?

Frequent updates are fine, but use a low importance channel (IMPORTANCE_LOW) with badges, vibration, and sound disabled. High importance with frequent updates alerts the user every time and is the most common reason support tickets get opened for this pattern.

Do Live Updates appear correctly in message reports?

Partly, and the gaps are worth knowing before you read the numbers. Each start, update, and end is a separate push, so a single Live Update produces one message report per event rather than one report for the whole lifecycle. Delivery still tracks normally, including Confirmed Delivery, because the SDK reports the push as received before your extension decides how to render it. Clicks do not: the user taps a notification your app posted through NotificationManager, not OneSignal’s, so OneSignal cannot attribute the tap and click metrics stay at zero.

What happens when the user swipes the Live Update away?

Your next update push reposts it, unless you record the dismissal. The extension runs fresh on every push and has no memory of what the user did, so Step 1 wires a setDeleteIntent and an Unpin action to a receiver that writes the dismissal to SharedPreferences, and skips update events while that record exists. Android tells apps not to repost dismissed Live Updates, and reposting is one of the reasons users revoke promotion permission. A later start for the same key clears the record, so the next activity displays normally.

What happens if the user kills the app or device reboots?

The Android notification persists on the lock screen and notification shade until your app cancels it or the user dismisses it. The service extension only runs when a push arrives, so any in-memory state (like progress) must be encoded in event_updates, not held in app memory.

Can I do this on iOS too?

iOS has a managed equivalent: Live Activities. It uses ActivityKit and is built into OneSignal as a first-class feature with its own Start and Update APIs. Use Live Activities on iOS rather than this pattern.

Service extensions

Intercept and customize push notifications before display on Android and iOS.

Create message API

The API used to send the start, update, and end pushes for a Live Update.

Live Activities (iOS)

The managed iOS equivalent built on ActivityKit.

Android notification categories

Configure channels so frequent Live Update updates don’t alert the user.