Skip to main content

Manage Multiple Calls

The Telnyx WebRTC JS SDK supports multiple simultaneous calls within a single TelnyxRTC client session. This guide covers concurrent call management, per-call media elements, call waiting, hold/transfer patterns, and the warnings the SDK emits when multiple calls are active.
The SDK does not recommend having multiple active calls simultaneously and cannot guarantee stable behavior in all scenarios. The supported pattern is accepting and holding an inbound call while finishing an active one — the SDK handles this well. Running two fully active calls at the same time (both with bidirectional audio) is not recommended and may produce unpredictable media or signaling behavior.

Overview

A single TelnyxRTC instance connected to rtc.telnyx.com can have multiple active calls at the same time. Each call has its own:
  • Call ID (call.id) — unique per call leg
  • Direction (call.direction) — inbound or outbound
  • State (call.state) — ringing, trying, active, held, hangup, destroyed
  • PeerConnection — independent RTCPeerConnection per call
  • Media elementremoteElement / localElement (see Per-call media elements)

Per-call media elements

Starting with SDK v2.27.4, you can assign a distinct remoteElement (and localElement) per call. This is essential for concurrent calls — without it, all calls share the same audio element and the SDK emits a SHARED_REMOTE_ELEMENT_OVERWRITE warning when a second call overwrites the first call’s stream.

Why per-call elements matter

Outbound calls

Pass remoteElement at call creation:

Inbound calls

Pass remoteElement when answering:

Backward compatibility

Omitting the per-call params keeps the session-level client.remoteElement as the fallback default. This is backward compatible with existing single-call integrations — no changes are needed for apps that handle only one call at a time.

Call waiting

When a second inbound call arrives while a call is already active, the SDK emits a MULTIPLE_ACTIVE_CALLS_DETECTED warning (33010). This is diagnostic only — the SDK does not block the second call.

Accept the second call

Reject the second call


Hold and switch between calls

Use hold() and unhold() to switch between concurrent calls:
hold() sends a re-INVITE to the remote party to pause media. The call stays in the held state and can be resumed with unhold().

Warnings for multiple calls

See Error Handling for the full warning reference.

Transfer

Blind transfer and attended transfer work with multiple calls. To perform an attended transfer:

Server-dialed consult leg (auto-answered second inbound call)

Some attended-transfer and consult flows are driven from the server: while the agent is on their customer call, Call Control dials the agent’s own credential as a second leg and the client auto-answers it. Because both legs are inbound calls on the same single registration, this depends on the SDK allowing a second answer() for a distinct call ID.
In v2.27.0–v2.27.3 this second answer() was silently ignored: the duplicate-answer guard keyed on any other active inbound call, so the second leg stayed ringing with only a DUPLICATE_INBOUND_ANSWER (33007) warning and no error or state change (webrtc#726). v2.27.4 fixes this — the guard is scoped per call ID, so answering a genuinely distinct second inbound call proceeds normally. The previous call.options.attach = true workaround is no longer needed.
Auto-answer the server-dialed leg when it rings (identify it however your backend marks it — e.g. a custom SIP header):
A DUPLICATE_INBOUND_ANSWER (33007) warning now fires only when the same call ID is answered twice (for example a duplicate WebSocket registration of the same leg); it is warning-only and never tears down established media.

Best practices for concurrent calls

  1. One active call at a time — the SDK does not recommend having two fully active calls simultaneously. Use hold to manage the active call and accept/hold an inbound call while finishing the active one.
  2. Assign distinct remoteElement per call — use separate <audio> or <video> elements for each call to avoid SHARED_REMOTE_ELEMENT_OVERWRITE warnings and ensure independent playout lifecycles.
  3. Track calls by ID — use call.id as the key in your application state. The call ID may change after reconnection (see recoveredCallId).
  4. Clean up on destroyed — remove call references from your state when call.state === 'destroyed' to prevent memory leaks.
  5. Use hold() before answering — when accepting a second call, hold the first call first to avoid overlapping audio.
  6. One TelnyxRTC instance — keep a single client instance per tab/session. Multiple instances with the same credential cause DUPLICATE_INBOUND_ANSWER warnings.

See Also