// Make sure to use a unique id when creating ring calls
await client.call("default", crypto.randomUUID()).getOrCreate({
ring: true,
data: {
created_by_id: "myself",
members: [{ user_id: "myself" }, { user_id: "my friend" }],
},
});
Ring Calls
Creating ring calls
The Stream Video API offers a ringing flow that simulates traditional phone calls. This feature lets developers implement call experiences where users can receive incoming calls with ringtones and choose to accept or decline.
You can also show missed call notifications if someone doesn’t answer the call on time or is busy in another call.
To start the ringing flow, we need to set the ring
flag to true
and provide the list of members we want to call. It is important to note that the caller should also be included in the list of members.
Stream API allows reusing call ids (for example, to implement recurring meetings), but this is not something we advise for ring calls. You should create a unique id for every ring call (for example by using UUIDs).
from getstream.models import CallRequest, MemberRequest
import uuid
# Make sure to use a unique id when creating ring calls
client.video.call("default", uuid.uuid4()).get_or_create(
data=CallRequest(
created_by_id=user_id,
members=[MemberRequest(user_id=alice.id), MemberRequest(user_id=bob.id)],
),
ring=True
)
import (
"github.com/google/uuid"
)
// Make sure to use a unique id when creating ring calls
client.Video().Call("default", uuid.New().String()).GetOrCreate(ctx, &getstream.GetOrCreateCallRequest{
Ring: getstream.PtrTo(true),
Data: &getstream.CallRequest{
CreatedByID: getstream.PtrTo("myself"),
Members: []getstream.MemberRequest{
{UserID: "myself"},
{UserID: "my friend"},
},
},
})
List<MemberRequest> members = List.of(
MemberRequest.builder().userID("myself").build(),
MemberRequest.builder().userID("my friend").build()
);
// Make sure to use a unique id when creating ring calls
var call = new Call("default", UUID.randomUUID().toString(), client.video());
call.getOrCreate(
GetOrCreateCallRequest.builder()
.data(
CallRequest.builder()
.createdByID("myself")
.members(members)
.build()
)
.ring(true)
.build()
);
CALL_TYPE='default'
# Make sure to use a unique id when creating ring calls
CALL_ID=$(uuidgen)
curl -X POST "https://video.stream-io-api.com/api/v2/video/call/${CALL_TYPE}/${CALL_ID}?api_key=${API_KEY}" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-H "stream-auth-type: jwt" \
-d '{
"ring": true,
"data": {
"created_by_id": "myself",
"members": [{ "user_id": "myself" }, { "user_id": "my friend" }]
}
}'
Accepting and rejecting ring calls
Accepting and rejecting calls are client-side operations, you can read more about them in SDK documentations.
Ring call events
The ring flow relies on the following events:
call.ring
call.accepted
call.rejected
call.missed
call.ring
event
This is the start of the ring flow. This event is dispatched to call members when someone starts a call with the ring
flag set to true
.
It’s possible to send push notifications to call members on this event, for more information see Notification settings section
call.accepted
event
This event is dispatched to the call members when a callee accepts an incoming call.
call.rejected
event
This event is dispatched to the call members when the caller or callee rejects the call.
There are multiple reasons why someone could reject a call (for example, someone explicitly rejecting a call with a “Decline” button is different from someone not answering the call). The call.rejected
event contains a reason
field to indicate why a call was rejected.
You can pass any string to the reason
field, but the Stream API and the SDKs recognize the following values by default:
Name | Description |
---|---|
rejected | The callee explicitly rejected the call (for example, with a “Decline” button) |
cancel | The caller cancelled the call before it was answered. SDKs will also send this event if all callees rejected the call. |
timeout | 1. When a callee is online but doesn’t answer the call in incoming_call_timeout_ms time. 2. When no callee accepts the call in auto_cancel_timeout_ms time, the caller will send this event |
busy | Signals that the callee is busy. By default, SDKs don’t send this reason; integrators can implement this in their own application logic. |
call.missed
event
The call.missed
event is dispatched to callees who didn’t answer (accept or reject) an incoming call within a given time.
The event is dispatched in the following scenarios:
- The callee rejected the call with a
timeout
orbusy
reason - When a callee didn’t accept or reject the call in
missed_call_timeout_ms
time (probably the user was offline) - The caller rejected the call with any reason, unless the callee previously rejected the call (declined the call)
It’s possible to send push notifications to call members on this event, for more information see Notification settings section
Examples
SDKs implement ringing flow in a way that while a call is in the ringing phase, there is no active session. The session starts when the first callee accepts the call.
All accept
Example: Sara calls Ben and Jane. Both Ben and Jane accept the call.
- Sara rings Ben and Jane (with
ring: true
):call.created
andcall.ring
events from Sara to all call members - ringing flow starts - Ben accepts the call:
call.accepted
event from Ben - Ben joins the call:
call.session_started
andcall.session_participant_joined
events - As soon as Ben accepts the call Sara also joins:
call.session_participant_joined
- Jane also accepts, and joins:
call.accepted
andcall.session_participant_joined
- Jane leaves the call:
call.participant_left
- Ben and Sara also leave: 2x
call.participant_left
call.session_ended
wheninactivity_timeout_seconds
elapsed since last participant left the call
Someone rejects
Example: Sara calls Ben and Jane. Ben accepts the call, Jane rejects.
- Sara rings Ben and Jane (with
ring: true
):call.created
andcall.ring
events from Sara to all call members - ringing flow starts - Ben accepts the call:
call.accepted
event from Ben - Ben joins the call:
call.session_started
andcall.session_participant_joined
- As soon as Ben accepts the call Sara also joins:
call.session_participant_joined
- Jane declines the call:
call.rejected
event withreason: "decline"
from Jane - Ben and Sara leave the call: 2x
call.participant_left
call.session_ended
wheninactivity_timeout_seconds
elapsed since last participant left the call
All reject
Example: Sara calls Ben and Jane. Both Jane and Ben reject the call.
- Sara rings Ben and Jane (with
ring: true
):call.created
andcall.ring
events from Sara to all call members - ringing flow starts - Ben declines the call:
call.rejected
event withreason: "decline"
from Ben - Jane is online, but doesn’t interact with the incoming call screen:
call.rejected
event withreason: "timeout"
from Jane afterincoming_call_timeout_ms
elapsed call.missed
is sent to Jane- Since both Ben and Jane rejected the call Sara will reject as well (this is sent from SDKs):
call.rejected
event withreason: "cancel"
from Sara
Caller cancels
Example: Sara calls Ben and Jane, but Sara cancels before anyone answers.
- Sara rings Ben and Jane (with
ring: true
):call.created
andcall.ring
events from Sara to all call members - ringing flow starts - Sara changes her mind, and cancels:
call.rejected
event withreason: "cancel"
from Sara
No one answers the call
Example: Sara calls Ben and Jane, but no one answers.
- Sara rings Ben and Jane (with
ring: true
):call.created
andcall.ring
events from Sara to all call members - ringing flow starts - No one accepts or rejects the call in
auto_cancel_timeout_ms
time (this can happen if users are offline, and away from their devices):call.rejected
event withreason: "timeout"
from Sara call.missed
events are sent to both Ben and Jane
Ring call settings
Timeouts
You can control how much time to wait before automatic call rejections and missed call events are dispatched.
RingSettingsRequest
Name | Type | Description | Constraints |
---|---|---|---|
auto_cancel_timeout_ms | integer | When none of the callees accept a ring call in this time a rejection will be sent by the caller with reason 'timeout' by the SDKs | Required, Minimum: 5000, Maximum: 180000 |
incoming_call_timeout_ms | integer | When a callee is online but doesn't answer a ring call in this time a rejection will be sent with reason 'timeout' by the SDKs | Required, Minimum: 5000, Maximum: 180000 |
missed_call_timeout_ms | integer | When a callee doesn't accept or reject a ring call in this time a missed call event will be sent | Minimum: 5000, Maximum: 180000 |
These can be set on the call type level or on the call level:
// on call type level
client.video.updateCallType({
name: callTypeName,
settings: {
ring: {
incoming_call_timeout_ms: 10000,
auto_cancel_timeout_ms: 15000,
missed_call_timeout_ms: 15000,
},
},
});
// or on call level
call.update({
settings_override: {
ring: {
incoming_call_timeout_ms: 10000,
auto_cancel_timeout_ms: 15000,
missed_call_timeout_ms: 15000,
},
},
});
# On call type level
client.video.update_call_type(
name=callTypeName,
settings={
"ring": {
"incoming_call_timeout_ms": 10000,
"auto_cancel_timeout_ms": 15000,
"missed_call_timeout_ms": 15000
}
}
)
# On call level
call.update(
settings_override={
"ring": {
"incoming_call_timeout_ms": 10000,
"auto_cancel_timeout_ms": 15000,
"missed_call_timeout_ms": 15000
}
}
)
// Call type level
client.Video().UpdateCallType(ctx, callTypeName, &getstream.UpdateCallTypeRequest{
Settings: &getstream.CallSettingsRequest{
Ring: &getstream.RingSettingsRequest{
IncomingCallTimeoutMs: 10000,
AutoCancelTimeoutMs: 15000,
MissedCallTimeoutMs: getstream.PtrTo(15000),
},
},
})
// On call level
call.Update(ctx, &getstream.UpdateCallRequest{
SettingsOverride: &getstream.CallSettingsRequest{
Ring: &getstream.RingSettingsRequest{
IncomingCallTimeoutMs: 10000,
AutoCancelTimeoutMs: 15000,
MissedCallTimeoutMs: getstream.PtrTo(15000),
},
},
})
# On call type level
curl -X PUT "https://video.stream-io-api.com/api/v2/video/calltypes/${CALL_TYPE_NAME}?api_key=${API_KEY}" \
-H "Authorization: ${TOKEN}" \
-H "stream-auth-type: jwt" \
-H "Content-Type: application/json" \
-d '{
"settings": {
"ring": {
"incoming_call_timeout_ms": 10000,
"auto_cancel_timeout_ms": 15000,
"missed_call_timeout_ms": 15000
}
}
}'
# On call level
curl -X PATCH "https://video.stream-io-api.com/api/v2/video/call/${CALL_TYPE}/${CALL_ID}?api_key=${API_KEY}" \
-H "Authorization: ${TOKEN}" \
-H "stream-auth-type: jwt" \
-H "Content-Type: application/json" \
-d '{
"settings_override": {
"ring": {
"incoming_call_timeout_ms": 10000,
"auto_cancel_timeout_ms": 15000,
"missed_call_timeout_ms": 15000
}
}
}'
Push notifications
You can enable/disable push notifications for the call.ring
and call.missed
events.
client.video.createCallType({
name: "<call type name>",
notification_settings: {
enabled: true,
call_notification: {
apns: {
title: "{{ user.display_name }} calls you",
body: "{{ user.display_name }} calls you",
},
enabled: true,
},
call_ring: {
apns: {
title: "{{ user.display_name }} calls you",
body: "{{ user.display_name }} calls you",
},
enabled: true,
},
call_live_started: {
enabled: true,
apns: {
title: "{{ call.display_name }} started",
body: "{{ user.display_name }} started",
},
},
call_missed: {
enabled: true,
apns: {
title: "missed call from {{ user.display_name }}",
body: "missed call from {{ user.display_name }}",
},
},
session_started: {
enabled: true,
apns: {
title: "{{ call.display_name }} started",
body: "{{ call.display_name }} started",
},
},
},
});
client.video.create_call_type(
name=call_type_name,
notification_settings={
"enabled": True,
"call_notification": {
"apns": {
"title": "{{ user.display_name }} calls you",
"body": "{{ user.display_name }} calls you"
},
"enabled": True
},
"call_ring": {
"apns": {
"title": "{{ user.display_name }} calls you",
"body": "{{ user.display_name }} calls you"
},
"enabled": True
},
"call_live_started": {
"enabled": True,
"apns": {
"title": "{{ call.display_name }} started",
"body": "{{ user.display_name }} started"
}
},
"call_missed": {
"enabled": True,
"apns": {
"title": "missed call from {{ user.display_name }}",
"body": "missed call from {{ user.display_name }}"
}
},
"session_started": {
"enabled": True,
"apns": {
"title": "{{ call.display_name }} started",
"body": "{{ call.display_name }} started"
}
}
}
)
client.Video().CreateCallType(ctx, &getstream.CreateCallTypeRequest{
Name: "test-call-type",
NotificationSettings: &getstream.NotificationSettings{
Enabled: true,
CallNotification: getstream.EventNotificationSettings{
APNS: getstream.APNS{
Title: "{{ user.display_name }} calls you",
Body: "{{ user.display_name }} calls you",
},
Enabled: true,
},
CallRing: getstream.EventNotificationSettings{
APNS: getstream.APNS{
Title: "{{ user.display_name }} calls you",
Body: "{{ user.display_name }} calls you",
},
Enabled: true,
},
CallLiveStarted: getstream.EventNotificationSettings{
Enabled: true,
APNS: getstream.APNS{
Title: "{{ call.display_name }} started",
Body: "{{ user.display_name }} started",
},
},
CallMissed: getstream.EventNotificationSettings{
Enabled: true,
APNS: getstream.APNS{
Title: "missed call from {{ user.display_name }}",
Body: "missed call from {{ user.display_name }}",
},
},
SessionStarted: getstream.EventNotificationSettings{
Enabled: true,
APNS: getstream.APNS{
Title: "{{ call.display_name }} started",
Body: "{{ call.display_name }} started",
},
},
},
})
curl -X POST "https://video.stream-io-api.com/api/v2/video/calltypes?api_key=${API_KEY}" \
-H "Authorization: ${TOKEN}" \
-H "stream-auth-type: jwt" \
-H "Content-Type: application/json" \
-d '{
"name": "<call type name>",
"notification_settings": {
"enabled": true,
"call_notification": {
"apns": {
"title": "{{ user.display_name }} calls you",
"body": "{{ user.display_name }} calls you"
},
"enabled": true
},
"call_ring": {
"apns": {
"title": "{{ user.display_name }} calls you",
"body": "{{ user.display_name }} calls you"
},
"enabled": true
},
"call_live_started": {
"enabled": true,
"apns": {
"title": "{{ call.display_name }} started",
"body": "{{ user.display_name }} started"
}
},
"call_missed": {
"enabled": true,
"apns": {
"title": "missed call from {{ user.display_name }}",
"body": "missed call from {{ user.display_name }}"
}
},
"session_started": {
"enabled": true,
"apns": {
"title": "{{ call.display_name }} started",
"body": "{{ call.display_name }} started"
}
}
}
}'