> This page is part of the [Customer.io documentation](https://docs.customer.io). For the complete index, see [llms.txt](https://docs.customer.io/llms.txt).
> Last updated: August 18, 2026

# Live Updates API and payload reference

Your server drives every live notification through the [Live Notifications API](/integrations/api/app/#tag/live-notifications). This page covers the operations, the two objects you send with each one, the bundled templates, and the FCM data message that arrives on the device.

For the app-side work—enabling notification types, branding the templates, and rendering custom types—see [Set up Live Updates](/integrations/sdk/android/live-notifications/set-up/).

## Notification types

Every live notification belongs to a **notification type**. Each type has an `identifier` in reverse-DNS notation, like `io.customer.order-tracking`, so the identifier stays unique. Identifiers work well in code but are hard to scan in a list, so you can also assign a friendly `name` that appears in your activity logs.

A type is the *kind* of thing you track—an order, a ride, a match. Each running notification is an **instance** of that type, with its own `instance_id`. Your update, end, and status calls all reference a notification by its `instance_id`.

Customer.io records a notification type automatically when it receives the type’s SDK registration or its first start request. You can view your types and edit their friendly names under [**Settings** > **Live notifications**](https://fly.customer.io/workspaces/last/settings/live-notification-types). There’s no create or delete action, and you can’t edit an identifier.

 ![The live notification types table in workspace settings, showing each type's friendly name and reverse-DNS identifier](https://docs.customer.io/images/live-activity-table.png)

## Activity lifecycle

Every live notification has a unique `instance_id` that you use to track its lifecycle and target its updates. Each instance holds every delivery made over the notification’s lifespan.

*   [When you start a notification from the API](/integrations/api/app/#tag/live-notifications/startLiveNotification), Customer.io generates the instance ID and returns it in the response.
*   When the notification starts from your app, the SDK generates it.

An instance can move through these states:

State

Meaning

`active`

The notification is running and can receive updates.

`ended`

Your app or server ended it.

`expired`

It passed its expiration without being ended.

`failed`

Customer.io couldn’t deliver to it.

### Start a notification

You can start a live notification two ways:

*   **From your app**: Your app starts the notification on the device—the moment a customer places an order, for example. This works while your app is in the foreground.
*   **From your server**: Your server calls the [start endpoint](/integrations/api/app/#tag/live-notifications/startLiveNotification) and Customer.io pushes to the device. Your app doesn’t need to be open.

Either way, the SDK reports the notification to Customer.io, and Customer.io pushes updates to it from that point on.

### Update a running notification

Your server updates live notifications through the [update endpoint](/integrations/api/app/#tag/live-notifications/updateLiveNotification), referencing the notification by its instance ID. Customer.io looks up the notification’s token and pushes the new content state to the device. The operating system re-renders it without launching your app.

Each update must carry the **full** content state. You can’t send partial updates that contain only the fields that changed.

Your app can also update a notification locally through the SDK. But SDK-side updates only change the notification on the device. The update doesn’t appear in the Customer.io delivery timeline. (Note that SDK-managed local starts and ends *do* appear in the delivery timeline.)

### End a notification

There are three ways to end a live notification:

*   **Your server** sends an [end request](/integrations/api/app/#tag/live-notifications/endLiveNotification) to Customer.io, which delivers a final push that transitions the notification to its ended state. You can include a final `content_state` to show before it ends; it defaults to the last state you sent.
*   **Your app** ends it locally, and the SDK reports the end to Customer.io.
*   **Automatic expiration.** Customer.io marks a notification `expired` 6 hours after it starts by default. You can set an earlier expiration with the `expiration` field on the [start request](/integrations/api/app/#tag/live-notifications/startLiveNotification), but 6 hours is the maximum. Expiration changes the status in Customer.io—it doesn’t send an end push or clear the notification from the device.

Server control and app control aren’t exclusive: you can start an activity in your app for instant feedback and hand it to your server for updates, or start it from your server and end it in your app. However, local updates never enter the delivery timeline, so a notification you drive from both places will have gaps in its Customer.io history.

### Review deliveries

Live notifications are a delivery channel in Customer.io. Every start, update, and end is a separate **delivery**, so one notification produces a timeline of deliveries over its lifespan. Each delivery moves through various statuses: `queued`, `sent`, `attempted`, `failed`, or `undeliverable`. In general, queued and sent deliveries are good states; failed and undeliverable deliveries mean your live notification didn’t make it to the device.

You can see every live notification sent to a person, and drill into each instance’s full delivery timeline, on their profile. See [Monitor live notifications](/messaging/channels/live-notifications/monitor/) for more.

## Attributes and content state

When you [start](/integrations/api/app/#tag/live-notifications/startLiveNotification), [update](/integrations/api/app/#tag/live-notifications/updateLiveNotification), or [end](/integrations/api/app/#tag/live-notifications/endLiveNotification) a live notification through the API, you send two objects: `attributes` and `content_state`. Customer.io wraps them in the right platform payload and delivers them to the device.

*   `attributes` describe the thing the notification is *about*—the order, the flight, the match. You set them when the notification starts, and they stay fixed for its lifetime.
*   `content_state` is what the notification *currently shows*—the status, the score, the ETA. It changes with every update, and the device re-renders from it each time.

When you design a notification type, that’s the test for where a field goes: if it can change while the notification is running, it belongs in `content_state`. If it identifies what the notification is tracking, it’s an attribute.

A few conventions apply:

*   **Dates and timestamps are epoch seconds**, not milliseconds. Use the Unix format.
*   **Each update carries the full content state.** The device re-renders from the complete state on every push. There are no partial updates.
*   **Static fields behave differently per platform.** On iOS, ActivityKit fixes `attributes` when the activity is created and keeps them for its lifetime, so you send them only on start. On Android, the device re-renders from each push, so re-send `attributes` on every update and end—otherwise the static fields disappear.

Send the static fields your renderer needs on every call

The Android SDK renders from a merged payload, so include any static fields your template or custom renderer reads in `attributes` on update and end calls too—not just on start.

## Built-in templates

The built-in templates are the lowest-effort way to ship a live notification: when one of the two shapes—a countdown or a step-by-step tracker—fits what you need, Customer.io renders it for you. You don’t have to write any UI code. You control the content the template shows (and, on Android, template branding like an accent color and logo), but the layout is fixed—you can’t rearrange a built-in template’s views. For anything else, you need to define a custom type and render it yourself.

Both built-in templates use the same [notification type identifiers](#notification-types) on iOS and Android, so one cross-platform notification can share a single `notification_type`. You send static fields in `attributes` and dynamic fields in `content_state`; each template below shows an example, followed by schemas for both objects. The fields the templates render verbatim (`header`, `title`, `status`, `substatus`) are freeform text, but the supported fields are only the ones in each schema—the bundled templates don’t expose generic status-color, image, or logo fields.

### Countdown timer

Use this template to create a countdown timer, with the notification type `io.customer.livenotifications.countdowntimer`. A complete start request wraps the template’s `attributes` and `content_state` (documented below) in the standard [start request](/integrations/api/app/#tag/live-notifications/startLiveNotification) fields—`identifiers` and `notification_type`:

```
{
    "identifiers": { "email": "person@example.com" },
    "notification_type": "io.customer.livenotifications.countdowntimer",
    "attributes": {
        "header": "Summer Sale"
    },
    "content_state": {
        "title": "Flash sale ends in",
        "statusMessage": "30% off everything",
        "endTime": 1753603600
    }
}
```

#### Attributes

*   headerstringrequired

    Top-row label of the notification.

#### Content state

*   titlestringrequired

    Primary status line.

*   statusMessagestring

    Secondary line under the title.

*   endTimeinteger

    Countdown target, in whole seconds since 1970 UTC. A time in the future renders a live countdown; omit it to render no timer. The countdown does not clear itself when it reaches zero — it rests at \\"0:00\\" until you send an update with a finished title and no endTime.

### Multi-step tracker

Use this template to create a delivery process tracker, or any tracker with steps, with the notification type `io.customer.livenotifications.segments`. A complete start request wraps the template’s `attributes` and `content_state` (documented below) in the standard [start request](/integrations/api/app/#tag/live-notifications/startLiveNotification) fields—`identifiers` and `notification_type`:

```
{
    "identifiers": { "email": "person@example.com" },
    "notification_type": "io.customer.livenotifications.segments",
    "attributes": {
        "header": "Chica's Tacos"
    },
    "content_state": {
        "status": "Preparing your order",
        "substatus": "We'll let you know when it's on the way",
        "segmentsTotal": 4,
        "segmentsComplete": 2,
        "trailingText": "25 min"
    }
}
```

Android shows one body line

Android has a single body slot, so `trailingText` appears only when `substatus` is empty. When you set both, `substatus` wins and `trailingText` is dropped.

#### Attributes

*   headerstringrequired

    Top-row label

#### Content state

*   statusstringrequired

    Primary status line, like `Out for delivery`.

*   substatusstring

    Secondary line under the status.

*   segmentsTotalintegerrequired

    The total number of segments in the progress bar. Values above 20 are capped at 20.

*   segmentsCompleteintegerrequired

    How many segments are filled; the remainder render as incomplete. Values above segmentsTotal are capped at segmentsTotal.

*   trailingTextstring

    Short text on the Dynamic Island trailing edge, e.g. \\"5 min\\". Keep it brief; the trailing region is narrow.

## What Customer.io sends to FCM

Android live notifications arrive as FCM **data messages**. There’s no `notification` block, so the SDK renders them even when your app is in the background:

```
{
    "data": {
        "cioInstanceId": "01J4YQZC3GJ0S6RY4E5NW6H9AB",
        "event": "update",
        "notification_type": "io.customer.livenotifications.segments",
        "timestamp": "1721000000",
        "link": "yourapp://orders/1234",
        "payload": "{\"header\":\"Order\",\"status\":\"Out for delivery\",\"segmentsTotal\":4,\"segmentsComplete\":3}"
    }
}
```

*   `cioInstanceId` identifies the notification. Updates with the same ID replace the notification in place.
*   `event` is `start`, `update`, or `end`.
*   `notification_type` selects the template, or your custom renderer.
*   `timestamp` is what the SDK uses to discard out-of-order and duplicate deliveries.
*   `payload` is a JSON string of your merged `attributes` and `content_state`.

[GitHub project](https://github.com/customerio/customerio-android "View the Android SDK on GitHub")

Version

4.20.1 (Current)3.x (3.11.2)2.x (2.1.1)

©2026 Peaberry Software, Inc. [Status](https://status.customerio.com/) [Terms of Service](https://customer.io/legal/terms-of-service/) [Privacy Policy](https://customer.io/legal/privacy-policy/)

[](https://www.linkedin.com/company/customer-io)[](https://twitter.com/customerio)[](https://www.youtube.com/channel/UCkCaWdezRoa8ZyR9pEVaipA)[](https://www.instagram.com/customer.io/)
