Error Handling
The SDK exposes error-related behavior through three main channels:| Event | Purpose | Recommended use |
|---|---|---|
telnyx.error | Fatal or blocking SDK errors | Show actionable errors, retry, re-authenticate |
telnyx.warning | Non-fatal quality, connectivity, and token warnings | Show degraded-state UI, collect telemetry |
telnyx.notification | Call lifecycle updates and compatibility notifications | Drive call UI and hangup handling |
telnyx.ready to know when the client is authenticated and the gateway is ready. Do not treat readiness as a notification case.
What your application should react to
For production integrations, handle these events explicitly:| Event | React in UI? | Retry/recover yourself? | Notes |
|---|---|---|---|
telnyx.ready | Yes | No | Connection is authenticated and ready for calls. Hide reconnecting state here. |
telnyx.error | Yes | Sometimes | Fatal/blocking errors. Follow the error code guidance below. |
telnyx.warning | Yes for call-affecting warnings | Usually no | Degraded but non-fatal. The SDK continues running and often starts automatic recovery. |
telnyx.notification with type: 'callUpdate' | Yes | No | Source of truth for call states, hangups, SIP cause/causeCode, and recovered calls. |
telnyx.socket.close / telnyx.socket.error | Optional | No unless autoReconnect: false | Useful for telemetry and reconnecting UI; wait for telnyx.ready or RECONNECTION_EXHAUSTED. |
Version note: The structured error and warning system (TELNYX_ERROR_CODES,telnyx.warning,TelnyxError) was introduced after v2.25.25. Exponential-backoff reconnection and browseronline/offlinehints (see Reconnection Behavior) ship in the next release. If you are on v2.25.25, see Error handling in v2.25.25 below.
Structured Errors (telnyx.error)
telnyx.error is the primary error surface. Listen for it to handle authentication failures, media errors, and connection issues.
Imports
Error event payload structure
Everytelnyx.error event is one of two shapes. Always check isMediaRecoveryErrorEvent(event) first, because the media-recovery variant carries callable recovery helpers.
Standard error event (the common case):
mediaPermissionsRecovery.enabled is set and getUserMedia() fails while answering):
error object (TelnyxError / ITelnyxError) exposes:
| Field | Type | Meaning |
|---|---|---|
code | number | Numeric error code (e.g. 48501). Use this for branching. |
name | string | Machine-readable name in UPPER_SNAKE_CASE (e.g. SESSION_NOT_REATTACHED). |
message | string | Short human-readable message for UI alerts. |
description | string | Full explanation of what happened and why. |
causes | string[] | Possible root causes. |
solutions | string[] | Suggested remediation steps. |
originalError | unknown | The underlying browser/SDK error, if any. |
fatal | boolean | true when the situation is terminal — the operation/call/session is dead and the client should take action. false when the SDK is handling recovery (auto-reconnect, gateway retry, media recovery, signaling recovery, etc.) or when the failure is benign enough to ignore. Always present on event.error. |
fatalvsrecoverable— two distinct fields, two distinct purposes:Recommended listener order: (1) check
event.error.fatal(always present on every error): tells your app whether the SDK has a recovery path.true= the SDK will not recover — take action now.false= the SDK is handling it — wait or continue.event.recoverable(only on media-permission recovery events): signals the app can recover via theresume()/reject()helpers. Onlyevent.recoverable === trueis meaningful;recoverableis absent on standard errors — do not branch onevent.recoverable === false.isMediaRecoveryErrorEvent(event)first, (2) then checkevent.error.fatalto decide whether to take action or wait, (3) then branch onevent.error.codefor tailored UX.
Getting and filtering errors by code
Media permission recovery
WhenmediaPermissionsRecovery.enabled is configured and getUserMedia() fails while answering a call, the error event includes recoverable: true with resume() and reject() callbacks:
Fatal errors and recommended handling
Fatal errors are situations the SDK will not recover from. When a fatal error fires, the affected call or session is dead — the SDK will not retry, reconnect, or reattach on its own. Your application must take the action described below. Every error event includesevent.error.fatal — a boolean that tells your app whether the SDK has a recovery path. true means the situation is terminal and you must act; false means the SDK is handling it (wait or continue). The Fatal? column below mirrors this field.
| Code | Name | Fatal? | What happens under the hood | Recommended handling |
|---|---|---|---|---|
40001 | SDP_CREATE_OFFER_FAILED | Fatal | RTCPeerConnection.createOffer() rejected by the browser; no offer SDP was generated, so the outbound call never starts. | Show the error; retry the outbound call with client.newCall(). Uncommon — if this recurs a few times, share the call with Telnyx support for investigation. |
40002 | SDP_CREATE_ANSWER_FAILED | Fatal | RTCPeerConnection.createAnswer() rejected by the browser; no answer SDP was generated, so the inbound call cannot be answered. | Show the error; the inbound call cannot be answered. Wait for a new inbound call or place an outbound call. Uncommon — if this recurs a few times, share the call with support for investigation. |
40003 | SDP_SET_LOCAL_DESCRIPTION_FAILED | Fatal | RTCPeerConnection.setLocalDescription() rejected the generated SDP — the local call configuration could not be applied. | Show the error; retry the call. Uncommon — if this recurs a few times, share the call with support for investigation. |
40004 | SDP_SET_REMOTE_DESCRIPTION_FAILED | Fatal | RTCPeerConnection.setRemoteDescription() rejected the remote SDP — the remote offer/answer may be malformed or contain unsupported codecs. | Show the error; retry the call. Check codec configuration if it recurs. Uncommon — if this recurs a few times, share the call with support for investigation. |
40005 | SDP_SEND_FAILED | Fatal | The Invite or Answer JSON-RPC message could not be sent — the WebSocket connection was likely lost before the message was delivered. | Show the error; check connectivity and retry the call. |
42001 | MEDIA_MICROPHONE_PERMISSION_DENIED | Fatal (unless mediaPermissionsRecovery enabled, inbound call answering only) | getUserMedia() rejected with NotAllowedError — the user or OS denied microphone permission. | Prompt the user to grant microphone permission in browser/OS settings. With mediaPermissionsRecovery.enabled (inbound calls only), a recoverable event is emitted instead — use resume()/reject(). |
42002 | MEDIA_DEVICE_NOT_FOUND | Fatal (unless mediaPermissionsRecovery enabled, inbound call answering only) | getUserMedia() rejected with NotFoundError/OverconstrainedError — no microphone matched the requested constraints or deviceId. | Check that a microphone is connected and the deviceId is valid. With recovery enabled (inbound calls only), handle via resume()/reject(). |
42003 | MEDIA_GET_USER_MEDIA_FAILED | Fatal (unless mediaPermissionsRecovery enabled, inbound call answering only) | getUserMedia() rejected for an unexpected reason — the device may be in use by another app or the browser hit an internal error. | Close other applications using the microphone and retry. With recovery enabled (inbound calls only), handle via resume()/reject(). |
44002 | INVALID_CALL_PARAMETERS | Fatal | The SDK validated the call options before sending anything to the server and found required parameters missing or invalid (e.g. no destinationNumber). | Fix the call parameters (e.g. provide a valid destinationNumber) before retrying. |
44005 | PEER_CLOSED_DURING_INIT | Fatal | The RTCPeerConnection was closed (e.g. by hangup()) while Peer.init() was still running — an async operation (setRemoteDescription, getUserMedia, or media-recovery) yielded control and close() ran during that gap. | Expected if the user intentionally hung up during setup — no action needed. If it recurs without user action, check for automatic hangup triggers firing too early. |
45001 | WEBSOCKET_CONNECTION_FAILED | Fatal | new WebSocket(...) threw synchronously — the server is unreachable, the URL is wrong, or a firewall blocks the connection. No socket object exists, so auto-reconnect cannot run. | Call client.connect() to retry. Uncommon — if this recurs a few times, share the call with support for investigation. |
45003 | RECONNECTION_EXHAUSTED | Fatal | The SDK exhausted all automatic reconnect attempts (default 10). Active calls are torn down locally before this fires. | Call client.disconnect() then client.connect() to start fresh, or recreate the client instance. Set maxReconnectAttempts: 0 for unlimited attempts. We recommend keeping autoReconnect enabled (the default) — disabling it is only for advanced use cases. |
46001 | LOGIN_FAILED | Fatal | The server rejected the login request (credentials invalid, expired, or account suspended). Registration never reached the ready state. | Re-authenticate using client.login() with fresh credentials; verify credentials and account status. Do not recreate the instance. |
46002 | INVALID_CREDENTIALS | Fatal | The SDK rejected the login options client-side before sending any request — the credentials object has missing or empty fields, or an invalid combination of credential fields. No network request was made. | Fix the credential parameters (ensure required fields like login_token are present and non-empty), then re-authenticate using client.login(). Check that the credential object matches one of the supported auth modes (credentials, token, or anonymous). |
48501 | SESSION_NOT_REATTACHED | Fatal | The WebSocket reconnected successfully, but the server did not reattach the active call session — the server lost the in-memory call state during the disconnection window. Subsequent call-control operations (hangup, hold, etc.) will fail with CALL_DOES_NOT_EXIST. | Terminate the local call and notify the user; start a new call. The call is unrecoverable. |
49001 | UNEXPECTED_ERROR | Fatal (default; emit sites override) | An error was thrown that does not match any known SDK error category — this is a catch-all for unclassified failures. | Inspect event.error.originalError; report to Telnyx support if it persists. |
Uncommon errors — when to investigate.Errors that are not fatal by default (the SDK handles or continues):SDP_CREATE_OFFER_FAILED(40001),SDP_CREATE_ANSWER_FAILED(40002),SDP_SET_LOCAL_DESCRIPTION_FAILED(40003),SDP_SET_REMOTE_DESCRIPTION_FAILED(40004),PEER_CLOSED_DURING_INIT(44005), andWEBSOCKET_CONNECTION_FAILED(45001) are not expected during normal operation. If any of these recurs a few times or more, share the call with Telnyx support for investigation — their occurrence is most likely not caused by user actions and may indicate a browser, network, or server-side issue.
HOLD_FAILED (44001), BYE_SEND_FAILED (44003), SUBSCRIBE_FAILED (44004), WEBSOCKET_ERROR (45002), GATEWAY_FAILED (45004), ICE_RESTART_FAILED (47001), NETWORK_OFFLINE (48001). Two exceptions to know:
AUTHENTICATION_REQUIRED(46003) is non-fatal by default but becomes fatal whenautoReconnect: false. Re-authenticate usingclient.login().- The three media errors (
42001–42003) become recoverable (non-fatal) whenmediaPermissionsRecovery.enabledis set.
Re-authenticate without recreating the instance. ForLOGIN_FAILED(46001),INVALID_CREDENTIALS(46002), andAUTHENTICATION_REQUIRED(46003), useclient.login()on the existing connection:
Errors and warnings you should handle explicitly
These are the high-impact errors and warnings we recommend handling explicitly in every production integration. They are also marked ⚠️ in the reference tables below. Important errors (handle ontelnyx.error):
| Code | Name | Why it matters | Recommended handling |
|---|---|---|---|
⚠️ 48501 | SESSION_NOT_REATTACHED | The server lost the call after reconnect; the call is unrecoverable | Terminate local call UI; let the user place a new call |
⚠️ 46001 | LOGIN_FAILED | Authentication rejected; the session will not become ready | Re-authenticate with client.login() and fresh credentials |
⚠️ 46002 | INVALID_CREDENTIALS | Credentials invalid before any network request | Fix credential params, then client.login() |
⚠️ 45003 | RECONNECTION_EXHAUSTED | Auto-reconnect gave up; the session is dead | client.disconnect() → client.connect(), or recreate the client |
⚠️ 42001 | MEDIA_MICROPHONE_PERMISSION_DENIED | No microphone access; call cannot have media | Prompt for mic permission; use mediaPermissionsRecovery for in-call recovery |
⚠️ 42002 | MEDIA_DEVICE_NOT_FOUND | No microphone / invalid deviceId | Check device connectivity and deviceId |
⚠️ 42003 | MEDIA_GET_USER_MEDIA_FAILED | getUserMedia() failed unexpectedly | Close other apps using the mic and retry |
telnyx.warning):
| Code | Name | Why it matters | Recommended handling |
|---|---|---|---|
⚠️ 35002 | UNKNOWN_REATTACHED_SESSION | Server tried to reattach a call the SDK no longer knows about | Inspect the Attach callId in the payload; start a new call if one should be active. This can happen in real scenarios — if an agent received a call they did not expect, share the call with Telnyx support for investigation. |
⚠️ 33010 | MULTIPLE_ACTIVE_CALLS_DETECTED | A new call started while another is active in the same session | Verify this is intentional (call waiting/transfer); otherwise hang up the previous call first |
⚠️ 36005 | RECONNECTION_FAILED_WITH_NO_AUTO_RECONNECT | Socket closed and auto-reconnect is disabled — the SDK will not reconnect | This warning is not expected unless you explicitly set autoReconnect: false. Review your application and call client.connect() to re-establish the session. We recommend keeping autoReconnect enabled (the default). |
⚠️ 36003 | SIGNALING_RECOVERY_REQUIRED | Signaling is unhealthy; SDK will force-close and reconnect | Show a short interruption state; wait for telnyx.ready and call reattach |
⚠️ 36004 | MEDIA_RECOVERY_REQUIRED | Media is unhealthy while signaling is healthy; SDK will ICE-restart | Show a media reconnecting indicator; keep call UI until recovery or call termination |
⚠️ 33007 | DUPLICATE_INBOUND_ANSWER | Two inbound calls answered in the same runtime → SIP 486 USER_BUSY risk | Keep a single TelnyxRTC instance; disconnect() before replacing; answer one inbound call at a time |
⚠️ 33006 | ANSWER_WHILE_PEER_ACTIVE | answer() called twice on the same call | Call answer() once per call; disable the answer button after first click |
⚠️ 33005 | ONLY_HOST_ICE_CANDIDATES | Only local-network candidates gathered; call will fail off-LAN | Verify STUN/TURN URLs/credentials and that UDP to STUN/TURN is not blocked |
⚠️ 33004 | PEER_CONNECTION_FAILED | RTCPeerConnection failed; SDK may ICE-restart | Show reconnecting/degraded UI; wait for SDK recovery; clean up only after call termination |
Error code reference
Each error below is classified as fatal or non-fatal and includes what the SDK does automatically versus what you should do.SDP errors
| Code | Name | Fatal? | Customer action | SDK behavior |
|---|---|---|---|---|
40001 | SDP_CREATE_OFFER_FAILED | Fatal | Show error to user; retry with client.newCall() | Call is not established |
40002 | SDP_CREATE_ANSWER_FAILED | Fatal | Show error to user; the inbound call cannot be answered | Call is rejected |
40003 | SDP_SET_LOCAL_DESCRIPTION_FAILED | Fatal | Show error to user; retry the call | Call setup fails |
40004 | SDP_SET_REMOTE_DESCRIPTION_FAILED | Fatal | Show error to user; retry the call | Call setup fails |
40005 | SDP_SEND_FAILED | Fatal | Show error to user; retry the call | Signaling could not be sent |
Media errors
| Code | Name | Fatal? | Customer action | SDK behavior |
|---|---|---|---|---|
⚠️ 42001 | MEDIA_MICROPHONE_PERMISSION_DENIED | Fatal unless mediaPermissionsRecovery is enabled | Prompt user to grant microphone permission in browser/OS settings | Call fails; if mediaPermissionsRecovery.enabled, a recoverable error is emitted instead |
⚠️ 42002 | MEDIA_DEVICE_NOT_FOUND | Fatal unless mediaPermissionsRecovery is enabled | Check that a microphone is connected and the deviceId is valid | Call fails |
⚠️ 42003 | MEDIA_GET_USER_MEDIA_FAILED | Fatal unless mediaPermissionsRecovery is enabled | Check browser permissions and device availability; retry | Call fails |
Call-control errors
| Code | Name | Fatal? | Customer action | SDK behavior |
|---|---|---|---|---|
44001 | HOLD_FAILED | Non-fatal | Retry hold operation | Hold is not applied |
44002 | INVALID_CALL_PARAMETERS | Fatal | Fix call parameters before retrying | Call is not established |
44003 | BYE_SEND_FAILED | Non-fatal | Call is still hung up locally; no action needed | Local hangup completes but BYE signal may not reach the server |
44004 | SUBSCRIBE_FAILED | Non-fatal | Check connection state; may need to reconnect | Cannot subscribe to call events |
44005 | PEER_CLOSED_DURING_INIT | Fatal | Retry the call | Peer connection closed before call setup completed |
ICE restart errors
| Code | Name | Fatal? | Customer action | SDK behavior |
|---|---|---|---|---|
47001 | ICE_RESTART_FAILED | Non-fatal | The call may recover via WebSocket reconnect + Attach; if not, hang up and retry | Media recovery could not complete via ICE restart |
WebSocket and transport errors
| Code | Name | Fatal? | Customer action | SDK behavior |
|---|---|---|---|---|
45001 | WEBSOCKET_CONNECTION_FAILED | Fatal | Call client.connect() to retry (no socket exists for auto-reconnect) | Session is not established |
45002 | WEBSOCKET_ERROR | Non-fatal | Show reconnecting UI; SDK auto-reconnects by default. If autoReconnect: false, call client.connect() manually. Wait for telnyx.ready | WebSocket error occurred; SDK schedules connect() via exponential backoff when autoReconnect is not disabled |
45003 | RECONNECTION_EXHAUSTED | Fatal | All automatic reconnect attempts exhausted. Call client.disconnect() then client.connect(), or recreate the client | Automatic reconnect stopped after maxReconnectAttempts attempts (default 10); set maxReconnectAttempts: 0 for unlimited attempts |
45004 | GATEWAY_FAILED | Non-fatal | Show reconnecting UI; SDK auto-reconnects by default. If autoReconnect: false, call client.connect() manually. Wait for telnyx.ready | Gateway reported FAILED / FAIL_WAIT / TIMEOUT; SDK retries until RECONNECTION_EXHAUSTED when autoReconnect is not disabled |
autoReconnectis enabled by default. Unless you explicitly setautoReconnect: false, the SDK handles reconnection automatically forWEBSOCKET_ERROR,GATEWAY_FAILED, and signaling-health recovery. You only need to callclient.connect()manually if you disabledautoReconnector afterRECONNECTION_EXHAUSTED.
Authentication and session errors
| Code | Name | Fatal? | Customer action | SDK behavior |
|---|---|---|---|---|
46001 | LOGIN_FAILED | Fatal | Re-authenticate using client.login() without recreating the instance | Registration never reached ready state |
46002 | INVALID_CREDENTIALS | Fatal | Fix credential parameters; re-authenticate using client.login() | Login was rejected before request was sent |
46003 | AUTHENTICATION_REQUIRED | Fatal when autoReconnect: false; otherwise non-fatal | Re-authenticate using client.login({ creds: { login_token: newToken } }) without recreating the instance | Request was sent before auth completed or after auth was lost. SDK re-auths on reconnect when autoReconnect is enabled |
48001 | NETWORK_OFFLINE | Non-fatal | Restore network connectivity; the SDK recovers via health signals, not the browser online event alone | Browser offline event fired. Emitted for backward compatibility; recovery is driven by SDK-owned health signals |
48501 | SESSION_NOT_REATTACHED | Fatal | Terminate the local call and notify the user; start a new call | The WebSocket reconnected but the server did not reattach the call; subsequent call-control fails with CALL_DOES_NOT_EXIST |
49001 | UNEXPECTED_ERROR | Fatal | Check event.error.originalError for details; report if it persists | Unclassified failure during peer/call setup |
Re-authenticate without recreating the instance. ForLOGIN_FAILED(46001),INVALID_CREDENTIALS(46002), andAUTHENTICATION_REQUIRED(46003), useclient.login()to re-authenticate on the existing connection:
Structured Warnings (telnyx.warning)
Warnings are never fatal. They describe degraded behavior, quality issues, or situations that may need user action before the session breaks. The SDK continues operating after emitting a warning.
Imports
Warning event payload structure
Every warning event includes a structuredwarning object and the SDK sessionId. When a warning is associated with a specific call, callId is included. Recovery-related warnings add reason (and source for signaling recovery) for diagnostics:
warning object (ITelnyxWarning) exposes:
| Field | Type | Meaning |
|---|---|---|
code | number | Numeric warning code (e.g. 33004). Use this for branching. |
name | string | Machine-readable name in UPPER_SNAKE_CASE. |
message | string | Short human-readable message for UI alerts. |
description | string | Full explanation of the warning. |
causes | string[] | Possible root causes. |
solutions | string[] | Suggested remediation steps. |
warning.code for application logic. Use warning.message, warning.causes, and warning.solutions for support tooling or user-facing troubleshooting copy.
Getting and filtering warnings by code
Warning code reference
Network quality warnings
| Code | Name | Auto-recovered? | Customer action |
|---|---|---|---|
31001 | HIGH_RTT | May self-resolve | Show quality indicator; no immediate action needed |
31002 | HIGH_JITTER | May self-resolve | Show quality indicator; no immediate action needed |
31003 | HIGH_PACKET_LOSS | May self-resolve | Show quality indicator; no immediate action needed |
31004 | LOW_MOS | May self-resolve | Show quality indicator; consider advising user |
31005 | LOW_LOCAL_AUDIO | May self-resolve | Show microphone-level indicator; ask the user to check mute/input gain or selected microphone |
31006 | LOW_INBOUND_AUDIO | May self-resolve | Verify the remote party is not muted/sending silence; check the media bridge for comfort-noise injection; inspect PCAP RTP payload if needed |
Data-flow warnings
| Code | Name | Auto-recovered? | Customer action |
|---|---|---|---|
32001 | LOW_BYTES_RECEIVED | May self-resolve on reconnect | Check remote party; show degraded audio indicator |
32002 | LOW_BYTES_SENT | May self-resolve on reconnect | Check local microphone; show degraded audio indicator |
Call connection warnings
| Code | Name | Auto-recovered? | Customer action |
|---|---|---|---|
33001 | ICE_CONNECTIVITY_LOST | SDK attempts ICE reconnect | Show reconnecting indicator; wait for recovery or PEER_CONNECTION_FAILED |
33002 | ICE_GATHERING_TIMEOUT | May self-resolve | Check firewall/STUN/TURN config; show warning |
33003 | ICE_GATHERING_EMPTY | No | Check network/firewall settings; STUN/TURN may be blocked |
⚠️ 33004 | PEER_CONNECTION_FAILED | May recover — SDK may attempt ICE restart | Show reconnecting/degraded UI; wait for SDK recovery; only clean up after call termination |
⚠️ 33005 | ONLY_HOST_ICE_CANDIDATES | No | Check STUN/TURN config; call may work on local network only |
⚠️ 33006 | ANSWER_WHILE_PEER_ACTIVE | No | Ensure answer() is called only once per call; disable the answer button after the first click; check that answer() is not invoked from multiple event handlers |
33008 | ICE_CANDIDATE_PAIR_CHANGED | Usually yes | Log candidate path changes and monitor quality; frequent changes indicate unstable network, VPN, NAT rebinding, or relay fallback |
33009 | AUDIO_INPUT_DEVICE_CHANGE_SKIPPED | No | Retry after the call is active and local media is attached; verify the call was started with audio enabled |
⚠️ 33010 | MULTIPLE_ACTIVE_CALLS_DETECTED | No (diagnostic only) | Verify this is expected (call waiting/transfer); otherwise hang up the previous call before creating a new one; use call.hold() if needed |
⚠️ 33007 | DUPLICATE_INBOUND_ANSWER | No | Keep a single active TelnyxRTC instance for inbound calls; disconnect old clients before replacing them; answer only one duplicate inbound notification |
Authentication and session warnings
| Code | Name | Auto-recovered? | Customer action |
|---|---|---|---|
34001 | TOKEN_EXPIRING_SOON | No, but preventable | Refresh the token before it expires; you have ~120 seconds |
⚠️ 35002 | UNKNOWN_REATTACHED_SESSION | No | Inspect the Attach callId in the warning payload; if a call should be active, start a new call manually |
Signaling health and recovery warnings
| Code | Name | Auto-recovered? | Customer action |
|---|---|---|---|
⚠️ 36003 | SIGNALING_RECOVERY_REQUIRED | SDK reconnects signaling and reattaches active calls | Show short interruption state; wait for call reattach or call termination. source indicates the trigger: probe, request, peer_failure, or no_rtp |
⚠️ 36004 | MEDIA_RECOVERY_REQUIRED | SDK attempts ICE restart without reconnecting the socket | Show media reconnecting indicator; keep call UI active until recovery or call termination |
⚠️ 36005 | RECONNECTION_FAILED_WITH_NO_AUTO_RECONNECT | No — auto-reconnect is disabled | This warning is not expected unless you explicitly set autoReconnect: false. Review your application and call client.connect() to re-establish the session. We recommend keeping autoReconnect enabled (the default). |
OPEN, but no signaling bytes are flowing after a network interface change, VPN change, NAT timeout, or proxy/load-balancer drop. The SDK decides one recovery path:
- If signaling is unhealthy, it reconnects the WebSocket and reattaches active calls (
SIGNALING_RECOVERY_REQUIRED). - If signaling is healthy but media is unhealthy, it attempts ICE restart (
MEDIA_RECOVERY_REQUIRED). - It does not run both recovery paths at the same time.
online/offline events are treated as low-confidence hints and may accelerate a signaling health probe, but they do not directly trigger recovery.
Your application should keep the current call visible, show a reconnecting/degraded state, and wait for the next callUpdate, telnyx.ready, warning, or final hangup before cleaning up the UI.
Call Termination Data
When a call reacheshangup, inspect these fields on the Call object:
| Field | Type | Meaning |
|---|---|---|
cause | string | null | High-level cause (USER_BUSY, CALL_REJECTED, etc.) |
causeCode | number | null | Numeric cause code |
sipCode | number | null | SIP response code when available |
sipReason | string | null | SIP reason phrase when available |
| Cause | Meaning |
|---|---|
NORMAL_CLEARING | Expected call completion |
USER_BUSY | Remote party was busy |
CALL_REJECTED | Remote party rejected the call |
NO_ANSWER | Call timed out unanswered |
UNALLOCATED_NUMBER | Dialed number is invalid or does not exist |
Socket Events
telnyx.socket.close
Delivers the browser CloseEvent. During a forced safety cleanup, the SDK emits a synthetic abnormal close with code: 1006 and wasClean: false.
Useful close codes:
| Code | Meaning |
|---|---|
1000 | Normal closure |
1001 | Going away |
1006 | Abnormal closure |
1011 | Internal error |
telnyx.socket.error
Delivers { error: ErrorEvent, sessionId: string }. Browsers expose very little information for WebSocket errors. The SDK also emits telnyx.error with code 45002 (WEBSOCKET_ERROR) when ws.onerror fires.
Connection State Helpers
The browser session exposes WebSocket state helpers onclient.connection:
| Getter | Meaning |
|---|---|
client.connection.connected | WebSocket is in OPEN |
client.connection.isAlive | CONNECTING or OPEN |
client.connection.isDead | CLOSING or CLOSED |
Reconnection Behavior
Ontelnyx.socket.close or telnyx.socket.error, the SDK clears subscriptions and resets gateway readiness state. autoReconnect is enabled by default; unless you set autoReconnect: false, the SDK automatically schedules connect(). Automatic reconnect stops after maxReconnectAttempts attempts (default: 10), or runs indefinitely when maxReconnectAttempts: 0.
Reconnect backoff
Reconnect attempts use exponential backoff with jitter (not a fixed/random delay):- Base delay starts at ~1s and doubles per attempt (~1s → ~2s → ~4s → ~8s → ~16s), capped at 30s.
- ±25% jitter is applied to avoid thundering-herd reconnects.
- The backoff counter resets only on a confirmed healthy registration (
REGED), not merely on socket open.
Browser online/offline handling
Browseronline/offline events are treated as low-confidence hints, not direct recovery triggers:
offlineemits theNETWORK_OFFLINE(48001) error for backward compatibility/telemetry and may accelerate a signaling health probe. It does not force a reconnect.onlineclears the browser-reported offline state for diagnostics but does not trigger recovery.- Recovery starts only from SDK-owned health evidence: liveness probe timeout, critical request timeout, peer failure, or no-RTP.
Socket close/error dedupe
When a socket fails, browsers commonly emit bothSocketError and SocketClose for the same disconnect. The SDK dedupes these by socket generation so a duplicate event cannot clear an already-scheduled reconnect timer or schedule a redundant reconnect. Stale events from an older, already-replaced socket are ignored.
Gateway retry behavior
- UNREGED / NOREG: Up to 5 registration retries with exponential backoff. After that,
LOGIN_FAILED(46001). - FAILED / FAIL_WAIT / TIMEOUT:
GATEWAY_FAILED(45004) emitted on first detection. The SDK retries with exponential backoff untilRECONNECTION_EXHAUSTED(45003).
Keeping media alive
IfkeepConnectionAliveOnSocketClose is true, the SDK preserves active peer connections while signaling reconnects. Recovery can create a new Call object with recoveredCallId.
Clearing reconnect stickiness
By default, the SDK reconnects to the sameb2bua-rtc instance. To break this stickiness and route to a different instance:
Note:clearReconnectToken()andskipLastVoiceSdkIdare available in@telnyx/webrtc@2.26.4.
Error Handling in v2.25.25
Important: If you are using SDK version 2.25.25, the error handling architecture is fundamentally different from the current version. This section documents the v2.25.25 error surface.
What is different in v2.25.25
| Feature | v2.25.25 | v2.26.0+ |
|---|---|---|
| Structured error codes | Not available | TELNYX_ERROR_CODES with numeric codes |
telnyx.warning event | Not available | Available with TELNYX_WARNING_CODES |
TelnyxError class | Not available | Structured error class with .code, .name, .message |
isMediaRecoveryErrorEvent() | Not available | Available for media permission recovery |
SDK_ERRORS / SDK_WARNINGS | Not available | Available for error/warning metadata |
| Reconnect backoff | Fixed/random 2-6s delay | Exponential backoff with jitter, capped at 30s |
| Browser online/offline | Directly drives reconnect | Low-confidence hints; recovery driven by SDK health signals |
| Primary error surface | telnyx.error (raw Error) + telnyx.notification | telnyx.error (structured) + telnyx.warning |
Error events in v2.25.25
In v2.25.25, errors are emitted throughtelnyx.error and telnyx.notification:
telnyx.error — Session-level errors with raw Error objects (no .code property):
telnyx.notification — Carries both call lifecycle updates and error information. This is the only recommended way to handle media, peer connection, and signaling errors in v2.25.25. Do not listen for telnyx.rtc.mediaError, telnyx.rtc.peerConnectionFailureError, or telnyx.rtc.peerConnectionSignalingStateClosed directly — those are internal events. Use telnyx.notification instead:
notification.type | Meaning | Fatal? | Customer action |
|---|---|---|---|
userMediaError | Media device access failed | Yes (for the call) | Prompt user for microphone permission; the SDK hangs up the call automatically |
peerConnectionFailureError | Peer connection failed | Peer connection not recoverable, but call may be recovered | Show reconnecting/degraded UI; the call may be restored automatically by the server via attach with recovering state; only clean up after a final hangup/call update confirms loss |
signalingStateClosed | Peer signaling state closed | Peer connection not recoverable, but call may still be recovered by server | Show reconnecting/degraded UI; the call may recover through auto-created recovering call with the same call_id; only clean up after a final hangup/call update confirms loss |
Authentication errors in v2.25.25
Login errors are emitted ontelnyx.error with a type field for invalid credentials. You can re-authenticate using client.login() without recreating the TelnyxRTC instance:
Reconnection in v2.25.25
Reconnection behavior differs from the current version:autoReconnectis enabled by default; the SDK automatically reconnects unless you setautoReconnect: false- Reconnect uses a fixed/random 2-6 second delay (current SDK uses exponential backoff with jitter, capped at 30s)
- Browser
online/offlineevents directly drive reconnect (current SDK treats them as low-confidence hints) - No
maxReconnectAttemptsoption (current SDK defaults to 10 attempts and supportsmaxReconnectAttempts: 0for unlimited attempts) - No
clearReconnectToken()method - No
skipLastVoiceSdkIdoption keepConnectionAliveOnSocketCloseis available
Migrating from v2.25.25 to the latest
If you are upgrading from v2.25.25 to the latest version:- Replace
telnyx.notificationerror handling — usetelnyx.errorfor fatal errors andtelnyx.warningfor non-fatal conditions. Keeptelnyx.notificationfor call lifecycle only. - Replace
notification.type === 'userMediaError'handling withtelnyx.errorlistener switching onevent.error.code(42001,42002,42003). - Replace
notification.type === 'peerConnectionFailureError'handling withtelnyx.warninglistener forPEER_CONNECTION_FAILED(33004). - Replace
notification.type === 'signalingStateClosed'handling withtelnyx.warninglistener for the appropriate warning code. - Replace
ERROR_TYPE.invalidCredentialsOptionschecks withevent.error.code === TELNYX_ERROR_CODES.INVALID_CREDENTIALS(46002). Useclient.login()to re-authenticate without recreating theTelnyxRTCinstance. - Import new symbols:
TelnyxError,TELNYX_ERROR_CODES,TELNYX_WARNING_CODES,isMediaRecoveryErrorEvent. - Note
SESSION_NOT_REATTACHEDis an error, not a warning: in v2.26.0+ it isTELNYX_ERROR_CODES.SESSION_NOT_REATTACHED(48501) ontelnyx.error(fatal), not a warning.UNKNOWN_REATTACHED_SESSION(35002) is the separate warning.