Call lifecycle

Depending on your use-case you might want to track special events related to a call, for example the start or end of a call. This guide will list the most notable events related to call lifecycle, and the order in which they sent.

For all call events please refer to the Events reference.

For time-sensitive events you can also set up push notifications on the call type level.

Creating a call

When a call is created a call.created event is sent to all call members and to the user who created the call.

Calls can be created ahead of time (for example meetings/livestreams in the future). Unless a call is created in backstage mode (more on that below), all users that have join-call capability can join the call.

Please note that the Stream API has an upsert behavior, so if you create a call with an ID that already exists, no new call will be created (instead it will be updated), and no call.created event will be sent.

call.created event reference

Ring and notify

You might want to notify users about important calls:

  • Ring calls mimic traditional phone calls - we have a separate Ring call guide detailing all events (such as call.ring) related to ringing. For phone-style calls we recommend a new call per ring, the single_use call lifecycle and the call.ring_settled webhook together, see the recommended setup.
  • If you don't want traditional phone calls, but want to notify members at a given time you can use the notify flag. Setting this flag will send call.notification event to all call members. You can also send push notifications on this event, more information in the call type settings guide. You can send multiple call.notification events during a call's lifecycle.

Call sessions

Starting a session

A call session starts when the first participant joins the call. A call.session_started event is sent to all users watching the call.

You can send push notifications when a session has been started, more information in the call type settings guide

call.session_started event reference

Participants

As participants join and leave the call session call.session_participant_joined and call.session_participant_left events are sent to all users watching the call.

call.session_participant_joined event reference, call.session_participant_left event reference

Ending a session

A call session ends when the last participant leaves the call and inactivity_timeout_seconds elapsed (defaults to 30s). You can set a custom value for inactivity_timeout_seconds, more information can be found here.

Ending or deleting a call will also end the session.

call.session_ended event reference

Multiple call sessions

At a given time only one session can be active for a call. But call IDs can be reused (for example, a recurring meeting), so a call can have multiple sessions.

Backstage mode

If a call type has backstage mode enabled (for example livestream), the call will be created in backstage mode, and only users with the join-backstage capability can join the call. For more information on backstage, see the backstage guide.

Backstage mode is mainly used in livestream use-cases where hosts might want to gather before/after a stream.

A call can enter and leave backstage mode multiple times.

Enter backstage mode

When a call enters backstage mode the call.updated event is sent to all users watching the call. You can check the event.call.backstage field to know if the call is in backstage mode.

When a call type has backstage mode enabled all calls are created in backstage mode. This means that when call.created is sent, the call is already in backstage mode (call.updated is not sent in this case).

call.updated event reference, call.created event reference

Leave backstage mode (go live)

When a call goes live the call.live_started event is sent to all users watching the call.

You can send push notifications when a call goes live, more information in the call type settings guide.

call.live_started event reference

Ending calls

Calls can be ended by using the end call API endpoint. This operation signals to users that a call isn't supposed to be joined anymore.

If there is an active session when the call is ended, the session will be terminated, and users will be kicked out of the call (call.session_ended event sent immediately).

After the session is terminated, the call.ended event is sent to all call members.

To join an ended call users need the join-ended-call permission. The exception is a call with the single_use lifecycle, the default on the built-in audio_call and agent types: once ended, it refuses every join whatever the permissions, and get-or-create on its call ID fails, so create a new call instead.

SDKs won't end calls by default.

call.ended event reference

Deleting calls

When a call is deleted it can no longer be joined, it's also not returned by the Stream API. For more information checkout the GDPR guide.

The call will be ended and then call.deleted event will be sent to all users watching the call.

call.deleted event reference

Examples

Below you will find examples for some common call lifecycle events.

Please note that the examples doesn't show all Stream events, just the ones relevant for call lifecycle. The full list can be found in the Events reference.

Livestream

Example: Sara and Ben host a livestream

  1. Sara creates the call: call.created event - call is in backstage mode, only hosts can join
  2. Sara joins the call: call.session_started and call.session_participant_joined
  3. Ben also joins: call.session_participant_joined
  4. Hosts are ready to start the livestream: call.live_started
  5. Livestream ends, hosts return to backstage mode: call.updated
  6. Ben and Sara leave the call: 2x call.session_participant_left
  7. call.session_ended when inactivity_timeout_seconds elapsed since last participant left the call

Ring call

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.

call.ring_settled is sent to your backend only, see ring call events. The call.ended steps happen only on a call with the single_use lifecycle; a reusable call stays open.

All accept

Example: Sara calls Ben and Jane. Both Ben and Jane accept the call.

  1. Sara rings Ben and Jane (with ring: true): call.created and call.ring events from Sara to all call members - ringing flow starts
  2. Ben accepts the call: call.accepted event from Ben
  3. Ben joins the call: call.session_started and call.session_participant_joined events
  4. As soon as Ben accepts the call Sara also joins: call.session_participant_joined
  5. Jane also accepts, and joins: call.accepted and call.session_participant_joined
  6. Everyone has answered, so the ring settles: call.ring_settled with outcome: "answered_all", settled_by: "jane" and settle_reason: "answered"
  7. Jane leaves the call: call.session_participant_left
  8. Ben and Sara also leave: 2x call.session_participant_left
  9. call.session_ended when inactivity_timeout_seconds elapsed since last participant left the call

Someone rejects

Example: Sara calls Ben and Jane. Ben accepts the call, Jane rejects.

  1. Sara rings Ben and Jane (with ring: true): call.created and call.ring events from Sara to all call members - ringing flow starts
  2. Ben accepts the call: call.accepted event from Ben
  3. Ben joins the call: call.session_started and call.session_participant_joined
  4. As soon as Ben accepts the call Sara also joins: call.session_participant_joined
  5. Jane declines the call: call.rejected event with reason: "decline" from Jane
  6. Everyone has answered, so the ring settles: call.ring_settled with outcome: "answered_some", settled_by: "jane" and settle_reason: "rejected"
  7. Ben and Sara leave the call: 2x call.session_participant_left
  8. call.session_ended when inactivity_timeout_seconds elapsed since last participant left the call

All reject

Example: Sara calls Ben and Jane. Both Jane and Ben reject the call.

  1. Sara rings Ben and Jane (with ring: true): call.created and call.ring events from Sara to all call members - ringing flow starts
  2. Ben declines the call: call.rejected event with reason: "decline" from Ben
  3. Jane is online, but doesn't interact with the incoming call screen: call.rejected event with reason: "timeout" from Jane after incoming_call_timeout_ms elapsed
  4. call.missed is sent to Jane
  5. Everyone has answered, so the ring settles: call.ring_settled with outcome: "mixed" (one decline, one miss), settled_by: "jane" and settle_reason: "timeout"
  6. Since both Ben and Jane rejected the call Sara will reject as well (this is sent from SDKs): call.rejected event with reason: "cancel" from Sara
  7. On a single_use call, Sara's reject ends the call: call.ended with reason: "RejectedByCaller"

Caller cancels

Example: Sara calls Ben and Jane, but Sara cancels before anyone answers.

  1. Sara rings Ben and Jane (with ring: true): call.created and call.ring events from Sara to all call members - ringing flow starts
  2. Sara changes her mind, and cancels: call.rejected event with reason: "cancel" from Sara, then call.ring_settled with outcome: "cancelled", settled_by: "sara" and settle_reason: "cancel", and call.missed sent to Ben and Jane
  3. On a single_use call, the call ends after the call.missed events: call.ended with reason: "RejectedByCaller". A late accept from Ben or Jane now fails with HTTP 400, and a late join with HTTP 403

No one answers the call

Example: Sara calls Ben and Jane, but no one answers.

  1. Sara rings Ben and Jane (with ring: true): call.created and call.ring events from Sara to all call members - ringing flow starts
  2. 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 with reason: "timeout" from Sara (the iOS SDK sends reason: "cancel")
  3. The ring settles, because Sara's SDK closed it: call.ring_settled with outcome: "cancelled", settled_by: "sara" and settle_reason: "cancel". Then call.missed events are sent to both Ben and Jane
  4. On a single_use call, Sara's reject ends the call: call.ended with reason: "RejectedByCaller"

If Sara's app is closed while it rings, her SDK never sends that reject. If Ben's and Jane's devices don't reject either, for example because they are offline, the server closes the ring at its missed-call timeout: call.ring_settled with outcome: "missed_all", settled_by: "server" and settle_reason: "timeout", then call.missed events to Ben and Jane. On a single_use call, the server then ends the call: call.ended with reason: "RingNotAnswered". A late accept from Ben or Jane fails with HTTP 400, and a late join with HTTP 403. A reusable call stays open.

Meeting with notification

Sara creates a meeting with Ben and Jane, and notifies them.

  1. Sara creates a meeting with Ben and Jane (with notify: true): call.created and call.notification events
  2. Sara joins the meeting when it's created: call.session_started and call.session_participant_joined
  3. Ben joins as well: call.session_participant_joined
  4. Then Jane joins: call.session_participant_joined
  5. The meeting ends, all participants leave the call: 3x call.session_participant_left
  6. call.session_ended when inactivity_timeout_seconds elapsed since last participant left the call

Recurring meeting

Sara has a weekly meeting with Ben and Jane

  1. Sara creates a meeting with Ben and Jane: call.created event
  2. Ben joins the meeting: call.session_started and call.session_participant_joined
  3. Sara joins as well: call.session_participant_joined
  4. Then Jane joins: call.session_participant_joined
  5. The meeting ends, all participants leave the call: 3x call.session_participant_left
  6. call.session_ended when inactivity_timeout_seconds elapsed since last participant left the call

Next week:

  1. Jane joins the meeting: call.session_started and call.session_participant_joined
  2. Sara joins as well: call.session_participant_joined
  3. Then Ben joins: call.session_participant_joined
  4. The meeting ends, all participants leave the call: 3x call.session_participant_left
  5. call.session_ended when inactivity_timeout_seconds elapsed since last participant left the call

Ending a call

Apart from the single_use ring examples above, no example so far contained a call.ended event. That's because by default calls are not ended, only when the end call endpoint is explicitly called. Let's see an example for this:

  1. Sara creates a meeting with Ben and Jane: call.created event
  2. Sara joins the meeting when it's created: call.session_started and call.session_participant_joined
  3. Ben joins as well: call.session_participant_joined
  4. Then Jane joins: call.session_participant_joined
  5. Sara ends the call: call.session_ended and call.ended events are sent