Skip to main content

Call State Lifecycle

A call is a state machine. Understanding every state and transition is essential for building a reliable UI and handling edge cases like reconnection, transfer, and one-way audio.

State Diagram


All States


Outbound Call States (Detailed)

newringing

What happens internally:
  1. SDK creates a PeerConnection
  2. ICE gathering starts (host → srflx → relay candidates)
  3. SDP offer created with codec preferences
  4. INVITE sent over WebSocket to VSP
  5. VSP translates to SIP INVITE → carrier
Your app: Show “Calling…” with a spinner. Don’t start the call timer yet.

ringing

What happens:
  • Remote phone is ringing (SIP 180 Ringing)
  • You may hear ringback tone (generated locally by the SDK or played from network)
Your app: Play ringback tone if SDK doesn’t auto-play it. Show “Ringing…” state.

ringingactive

What happens internally:
  1. SIP 200 OK received from carrier
  2. SDP answer processed — codecs and ICE candidates agreed
  3. DTLS handshake completes — media is encrypted
  4. SRTP audio starts flowing in both directions
  5. Audio element auto-created and attached to DOM
Your app: Start call timer. Show in-call controls (mute, hold, hangup). Check audio is playing.

Inbound Call States (Detailed)

ringing (incoming)

What happens internally:
  1. VSP receives SIP INVITE from carrier
  2. VSP pushes invite message to SDK over WebSocket
  3. SDK creates a Call object with state: 'ringing'
  4. telnyx.notification fires with callUpdate
Your app: Show incoming call UI. Play ringtone. Offer Accept/Reject buttons.

ringingactive (answer)

What happens internally:
  1. SDK sends 200 OK over WebSocket
  2. getUserMedia() — browser requests microphone permission
  3. ICE gathering starts
  4. SDP answer sent
  5. DTLS handshake
  6. Media flows
** Important:** call.answer() triggers getUserMedia(). If the user hasn’t granted microphone permission, the browser will show a permission dialog. The call won’t be fully active until permission is granted.

ringingdestroyed (reject)

What happens: SDK sends SIP 487 Request Terminated (or CANCEL if INVITE still in progress).

Active Call States

activeheld

What happens:
  1. SDK sends re-INVITE with sendonly media direction
  2. Remote party’s audio continues (they hear hold music if configured)
  3. Your audio stops sending (microphone muted at SIP level)
  4. Remote party receives a callUpdate with their call state changing

heldactive

What happens:
  1. SDK sends re-INVITE with sendrecv media direction
  2. Two-way audio resumes

Reconnecting State

What triggers it:
  • ICE connectivity checks fail (network change)
  • DTLS session breaks
  • WebSocket still connected but media path lost
What the SDK does:
  1. ICE restart — re-gathers candidates
  2. Attempts to re-establish DTLS
  3. If successful → call.state → 'active' (call resumes)
  4. If fails after timeout → call.state → 'destroyed' (call drops)
Your app: Show a “Reconnecting…” banner. Don’t hang up — let the SDK try to recover. See Handle Reconnection for details.

Destroyed State

All calls end up here. It’s terminal — no further transitions.
Common causes: Your app: Clean up UI. Upload call report. Show call summary if applicable.

State Transition Matrix


Common Pitfalls

Double answer

Fix: Guard against double answer in your UI:

Not handling destroyed

Missing reconnecting


See Also