{
"aps": {
"alert": {
"title": "Missed call",
"body": "You missed a call from John Doe"
},
"category": "stream.video",
"mutable-content": 1,
"sound": "default",
"thread-id": "video-default:call_id_123"
},
"stream": {
"sender": "stream.video",
"type": "call.missed",
"version": "v2",
"call_cid": "default:call_id_123",
"call_display_name": "",
"created_by_id": "john_doe",
"created_by_display_name": "John Doe",
"receiver_id": "artem",
"title": "Missed call",
"body": "You missed a call from John Doe"
}
}Overview
Non-ringing push notifications let Stream notify a user about a call-related events. They are regular push notifications (FCM/APN) — your app receives the payload, displays a banner, and handle notification interaction. Unlike ringing (which the SDK handles end-to-end), non-ringing notifications are not managed by the SDK. Stream sends push notifications directly to the device; the rest is your app's responsibility.
Notification types
Stream sends push notifications for the following call events. Each event can be enabled, disabled, or customized (title/body templates) in the call type settings or in corresponding section on the dashboard.
| Event | When it's sent |
|---|---|
call.ring | Dispatched to call members when someone starts a call with the ring flag set to true — the start of the ringing flow. Comes as a regular notification on Android. On iOS it is sent as a VoIP push. |
call.notification | A call was created and the user should be notified without ringing (e.g. group calls, generic call notifications). |
call.missed | A ringing call was not answered (neither accepted nor rejected) within the configured timeout. |
call.live_started | A backstage call went live (e.g. a livestream the user follows started). |
call.session_started | A call session started — i.e. the first participant joined an active call. |
call.ring is handled automatically by the SDK as part of the ringing setup — it powers the CallKit (iOS) and Telecom (Android) ringing UI. You don't need to handle it yourself. The remaining four events are non-ringing and can be managed inside your app depending on exact use cases.
All five arrive via the same transport — Firebase Cloud Messaging on Android and APNs on iOS — and carry a sender: "stream.video" marker in the payload so you can distinguish them from app's other notifications.
High-level flow
Non-ringing notifications are end-to-end your app's responsibility. The typical flow is:
- Obtain the device push token on the client — the FCM registration token on Android, the APNs device token on iOS.
- Register the token with Stream via
client.addDevice(token, provider, providerName). The provider name must match a push provider configured on the Stream Dashboard. - Handle the incoming push when it arrives. Parse the Stream payload, filter for non-ringing events, and display a local notification with the content you want users to see.
- Handle background / killed state. On Android, Stream sends data-only FCM messages, so your app must render the notification itself — typically from a background message handler. On iOS, Stream sends standard APNs alert pushes with
aps.alertpopulated; iOS renders the banner natively, so no extra work is needed on the iOS side to display it. - Handle notification taps to navigate the user to the relevant screen (e.g. the call screen identified by
call_cid). - Unregister on logout via
client.removeDevice(token)so Stream stops sending pushes to that device.
To receive non-ringing push notifications, your app needs to register a device push token with Stream via client.addDevice — the FCM registration token on Android and the APNs device token on iOS. Registration is idempotent, so it's safe to call regardless of whether the ringing setup is also enabled.
Note that the iOS VoIP token registered automatically as part of the ringing flow doesn't apply here — VoIP tokens are PushKit-only, so a separate APNs device token must be registered for non-ringing notifications.
Payload shape
Stream's push notification payload contains the following fields in data (Android) or nested under stream (iOS). On iOS, the payload also carries a standard aps block with alert.title / alert.body populated from the call type's push template — that's what iOS renders as the banner natively, with no app code required.
{
"data": {
"sender": "stream.video",
"type": "call.missed",
"version": "v2",
"call_cid": "default:call_id_123",
"call_display_name": "",
"created_by_id": "john_doe",
"created_by_display_name": "John Doe",
"receiver_id": "artem",
"title": "Missed call",
"body": "You missed a call from John Doe"
}
}| Field | Description |
|---|---|
sender | Always "stream.video" — use this to identify Stream pushes and filter out unrelated notifications from your app. |
type | The event name — one of call.ring, call.notification, call.missed, call.live_started, call.session_started (see the table above). |
version | The payload schema version. Currently v2. Useful for forward compatibility if Stream adds new fields in future versions. |
call_cid | The call identifier in <type>:<id> format (e.g. default:call_id_123). Use this to navigate to the call on tap or to fetch call details. |
call_display_name | The call's display name, if one was set when the call was created. Empty string when no display name was provided. |
created_by_id | ID of the user that created the call. |
created_by_display_name | Display name of the user that created the call. Empty string when the user has no display name. |
receiver_id | ID of the user the notification is addressed to — i.e. the currently signed-in user on the device receiving the push. |
title | Pre-rendered notification title from the call type's push template (placeholders like {{ user.display_name }} already substituted). May be empty when no template is configured. |
body | Pre-rendered notification body from the call type's push template, with placeholders substituted. May be empty when no template is configured. |