// 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 |
timeout | 1. When a callee is online but doesn’t answer the call in incoming_call_timeout_ms time. 2. When no callee answers 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
It’s possible to send push notifications to call members on this event, for more information see Notification settings section
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"
}
}
}
}'