Skip to main content

Error Handling

The SDK exposes error-related behavior through three main channels:
EventPurposeRecommended use
telnyx.errorFatal or blocking SDK errorsShow actionable errors, retry, re-authenticate
telnyx.warningNon-fatal quality, connectivity, and token warningsShow degraded-state UI, collect telemetry
telnyx.notificationCall lifecycle updates and compatibility notificationsDrive call UI and hangup handling
Use 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:
EventReact in UI?Retry/recover yourself?Notes
telnyx.readyYesNoConnection is authenticated and ready for calls. Hide reconnecting state here.
telnyx.errorYesSometimesFatal/blocking errors. Follow the error code guidance below.
telnyx.warningYes for call-affecting warningsUsually noDegraded but non-fatal. The SDK continues running and often starts automatic recovery.
telnyx.notification with type: 'callUpdate'YesNoSource of truth for call states, hangups, SIP cause/causeCode, and recovered calls.
telnyx.socket.close / telnyx.socket.errorOptionalNo unless autoReconnect: falseUseful for telemetry and reconnecting UI; wait for telnyx.ready or RECONNECTION_EXHAUSTED.
Do not treat every warning as a failed call. Media/signaling recovery warnings are intentionally emitted before the SDK attempts recovery, so your application can show a short degraded/reconnecting state while the SDK handles the recovery path.
Version note: The structured error and warning system (TELNYX_ERROR_CODES, telnyx.warning, TelnyxError) was introduced after v2.25.25. Exponential-backoff reconnection and browser online/offline hints (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

import {
  SwEvent,
  TelnyxError,
  TELNYX_ERROR_CODES,
  isMediaRecoveryErrorEvent,
} from '@telnyx/webrtc';

Error event payload structure

Every telnyx.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):
interface ITelnyxStandardErrorEvent {
  error: ITelnyxError;     // structured error (TelnyxError instance)
  sessionId: string;       // SDK session identifier
  callId?: string;         // present when the error is tied to a call
  recoverable?: false;     // usually absent — see note below
}
Media-permission recovery event (only when mediaPermissionsRecovery.enabled is set and getUserMedia() fails while answering):
interface ITelnyxMediaRecoveryErrorEvent {
  error: ITelnyxMediaError; // one of 42001 / 42002 / 42003
  sessionId: string;
  callId: string;
  recoverable: true;        // the only positive recovery signal
  retryDeadline: number;   // epoch ms after which the SDK stops waiting
  resume: () => void;       // retry media acquisition after permissions are fixed
  reject: () => void;       // abort recovery and let the call fail
}
The structured error object (TelnyxError / ITelnyxError) exposes:
FieldTypeMeaning
codenumberNumeric error code (e.g. 48501). Use this for branching.
namestringMachine-readable name in UPPER_SNAKE_CASE (e.g. SESSION_NOT_REATTACHED).
messagestringShort human-readable message for UI alerts.
descriptionstringFull explanation of what happened and why.
causesstring[]Possible root causes.
solutionsstring[]Suggested remediation steps.
originalErrorunknownThe underlying browser/SDK error, if any.
fatalbooleantrue 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.
fatal vs recoverable — two distinct fields, two distinct purposes:
  • 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 the resume()/reject() helpers. Only event.recoverable === true is meaningful; recoverable is absent on standard errors — do not branch on event.recoverable === false.
Recommended listener order: (1) check isMediaRecoveryErrorEvent(event) first, (2) then check event.error.fatal to decide whether to take action or wait, (3) then branch on event.error.code for tailored UX.

Getting and filtering errors by code

import {
  SwEvent,
  TelnyxError,
  TELNYX_ERROR_CODES,
  isMediaRecoveryErrorEvent,
} from '@telnyx/webrtc';

client.on(SwEvent.Error, (event) => {
  // 1. Media-permission recovery path (only when configured)
  if (isMediaRecoveryErrorEvent(event)) {
    openPermissionsDialog({
      deadline: event.retryDeadline,
      onRetry: () => event.resume(),
      onCancel: () => event.reject(),
    });
    return;
  }

  // 2. Guard against non-structured errors (legacy/edge paths)
  if (!(event.error instanceof TelnyxError)) {
    showErrorMessage('An unknown SDK error occurred.');
    return;
  }

  // 3. Branch on the numeric code — always compare against TELNYX_ERROR_CODES
  switch (event.error.code) {
    case TELNYX_ERROR_CODES.SESSION_NOT_REATTACHED:
      // Fatal: server lost the call after reconnect
      terminateCallUi(event.callId);
      showErrorMessage('The call could not be recovered. Please place the call again.');
      break;

    case TELNYX_ERROR_CODES.LOGIN_FAILED:
    case TELNYX_ERROR_CODES.INVALID_CREDENTIALS:
      // Fatal: re-authenticate without recreating the client
      refreshTokenAndLogin();
      break;

    case TELNYX_ERROR_CODES.RECONNECTION_EXHAUSTED:
      // Fatal: automatic reconnect gave up
      showReconnectButton();
      break;

    case TELNYX_ERROR_CODES.MEDIA_MICROPHONE_PERMISSION_DENIED:
    case TELNYX_ERROR_CODES.MEDIA_DEVICE_NOT_FOUND:
    case TELNYX_ERROR_CODES.MEDIA_GET_USER_MEDIA_FAILED:
      showMediaError(event.error.message);
      break;

    default:
      showErrorMessage(event.error.message);
  }
});

Media permission recovery

When mediaPermissionsRecovery.enabled is configured and getUserMedia() fails while answering a call, the error event includes recoverable: true with resume() and reject() callbacks:
const client = new TelnyxRTC({
  login_token: jwt,
  mediaPermissionsRecovery: {
    enabled: true,
    timeout: 10000,
  },
});

client.on(SwEvent.Error, (event) => {
  if (isMediaRecoveryErrorEvent(event)) {
    // Show a dialog asking the user to grant microphone permission
    // event.retryDeadline is the timestamp by which they must act
    showPermissionDialog({
      onGrant: () => event.resume(),
      onDismiss: () => event.reject(),
    });
  }
});

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 includes event.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.
CodeNameFatal?What happens under the hoodRecommended handling
40001SDP_CREATE_OFFER_FAILEDFatalRTCPeerConnection.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.
40002SDP_CREATE_ANSWER_FAILEDFatalRTCPeerConnection.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.
40003SDP_SET_LOCAL_DESCRIPTION_FAILEDFatalRTCPeerConnection.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.
40004SDP_SET_REMOTE_DESCRIPTION_FAILEDFatalRTCPeerConnection.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.
40005SDP_SEND_FAILEDFatalThe 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.
42001MEDIA_MICROPHONE_PERMISSION_DENIEDFatal (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().
42002MEDIA_DEVICE_NOT_FOUNDFatal (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().
42003MEDIA_GET_USER_MEDIA_FAILEDFatal (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().
44002INVALID_CALL_PARAMETERSFatalThe 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.
44005PEER_CLOSED_DURING_INITFatalThe 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.
45001WEBSOCKET_CONNECTION_FAILEDFatalnew 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.
45003RECONNECTION_EXHAUSTEDFatalThe 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.
46001LOGIN_FAILEDFatalThe 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.
46002INVALID_CREDENTIALSFatalThe 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).
48501SESSION_NOT_REATTACHEDFatalThe 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.
49001UNEXPECTED_ERRORFatal (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. 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), and WEBSOCKET_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.
Errors that are not fatal by default (the SDK handles or continues): 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 when autoReconnect: false. Re-authenticate using client.login().
  • The three media errors (4200142003) become recoverable (non-fatal) when mediaPermissionsRecovery.enabled is set.
Re-authenticate without recreating the instance. For LOGIN_FAILED (46001), INVALID_CREDENTIALS (46002), and AUTHENTICATION_REQUIRED (46003), use client.login() on the existing connection:
// Refresh token without recreating TelnyxRTC
await client.login({ creds: { login_token: newToken } });

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 on telnyx.error):
CodeNameWhy it mattersRecommended handling
⚠️ 48501SESSION_NOT_REATTACHEDThe server lost the call after reconnect; the call is unrecoverableTerminate local call UI; let the user place a new call
⚠️ 46001LOGIN_FAILEDAuthentication rejected; the session will not become readyRe-authenticate with client.login() and fresh credentials
⚠️ 46002INVALID_CREDENTIALSCredentials invalid before any network requestFix credential params, then client.login()
⚠️ 45003RECONNECTION_EXHAUSTEDAuto-reconnect gave up; the session is deadclient.disconnect()client.connect(), or recreate the client
⚠️ 42001MEDIA_MICROPHONE_PERMISSION_DENIEDNo microphone access; call cannot have mediaPrompt for mic permission; use mediaPermissionsRecovery for in-call recovery
⚠️ 42002MEDIA_DEVICE_NOT_FOUNDNo microphone / invalid deviceIdCheck device connectivity and deviceId
⚠️ 42003MEDIA_GET_USER_MEDIA_FAILEDgetUserMedia() failed unexpectedlyClose other apps using the mic and retry
Important warnings (handle on telnyx.warning):
CodeNameWhy it mattersRecommended handling
⚠️ 35002UNKNOWN_REATTACHED_SESSIONServer tried to reattach a call the SDK no longer knows aboutInspect 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.
⚠️ 33010MULTIPLE_ACTIVE_CALLS_DETECTEDA new call started while another is active in the same sessionVerify this is intentional (call waiting/transfer); otherwise hang up the previous call first
⚠️ 36005RECONNECTION_FAILED_WITH_NO_AUTO_RECONNECTSocket closed and auto-reconnect is disabled — the SDK will not reconnectThis 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).
⚠️ 36003SIGNALING_RECOVERY_REQUIREDSignaling is unhealthy; SDK will force-close and reconnectShow a short interruption state; wait for telnyx.ready and call reattach
⚠️ 36004MEDIA_RECOVERY_REQUIREDMedia is unhealthy while signaling is healthy; SDK will ICE-restartShow a media reconnecting indicator; keep call UI until recovery or call termination
⚠️ 33007DUPLICATE_INBOUND_ANSWERTwo inbound calls answered in the same runtime → SIP 486 USER_BUSY riskKeep a single TelnyxRTC instance; disconnect() before replacing; answer one inbound call at a time
⚠️ 33006ANSWER_WHILE_PEER_ACTIVEanswer() called twice on the same callCall answer() once per call; disable the answer button after first click
⚠️ 33005ONLY_HOST_ICE_CANDIDATESOnly local-network candidates gathered; call will fail off-LANVerify STUN/TURN URLs/credentials and that UDP to STUN/TURN is not blocked
⚠️ 33004PEER_CONNECTION_FAILEDRTCPeerConnection failed; SDK may ICE-restartShow 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

CodeNameFatal?Customer actionSDK behavior
40001SDP_CREATE_OFFER_FAILEDFatalShow error to user; retry with client.newCall()Call is not established
40002SDP_CREATE_ANSWER_FAILEDFatalShow error to user; the inbound call cannot be answeredCall is rejected
40003SDP_SET_LOCAL_DESCRIPTION_FAILEDFatalShow error to user; retry the callCall setup fails
40004SDP_SET_REMOTE_DESCRIPTION_FAILEDFatalShow error to user; retry the callCall setup fails
40005SDP_SEND_FAILEDFatalShow error to user; retry the callSignaling could not be sent

Media errors

CodeNameFatal?Customer actionSDK behavior
⚠️ 42001MEDIA_MICROPHONE_PERMISSION_DENIEDFatal unless mediaPermissionsRecovery is enabledPrompt user to grant microphone permission in browser/OS settingsCall fails; if mediaPermissionsRecovery.enabled, a recoverable error is emitted instead
⚠️ 42002MEDIA_DEVICE_NOT_FOUNDFatal unless mediaPermissionsRecovery is enabledCheck that a microphone is connected and the deviceId is validCall fails
⚠️ 42003MEDIA_GET_USER_MEDIA_FAILEDFatal unless mediaPermissionsRecovery is enabledCheck browser permissions and device availability; retryCall fails

Call-control errors

CodeNameFatal?Customer actionSDK behavior
44001HOLD_FAILEDNon-fatalRetry hold operationHold is not applied
44002INVALID_CALL_PARAMETERSFatalFix call parameters before retryingCall is not established
44003BYE_SEND_FAILEDNon-fatalCall is still hung up locally; no action neededLocal hangup completes but BYE signal may not reach the server
44004SUBSCRIBE_FAILEDNon-fatalCheck connection state; may need to reconnectCannot subscribe to call events
44005PEER_CLOSED_DURING_INITFatalRetry the callPeer connection closed before call setup completed

ICE restart errors

CodeNameFatal?Customer actionSDK behavior
47001ICE_RESTART_FAILEDNon-fatalThe call may recover via WebSocket reconnect + Attach; if not, hang up and retryMedia recovery could not complete via ICE restart

WebSocket and transport errors

CodeNameFatal?Customer actionSDK behavior
45001WEBSOCKET_CONNECTION_FAILEDFatalCall client.connect() to retry (no socket exists for auto-reconnect)Session is not established
45002WEBSOCKET_ERRORNon-fatalShow reconnecting UI; SDK auto-reconnects by default. If autoReconnect: false, call client.connect() manually. Wait for telnyx.readyWebSocket error occurred; SDK schedules connect() via exponential backoff when autoReconnect is not disabled
45003RECONNECTION_EXHAUSTEDFatalAll automatic reconnect attempts exhausted. Call client.disconnect() then client.connect(), or recreate the clientAutomatic reconnect stopped after maxReconnectAttempts attempts (default 10); set maxReconnectAttempts: 0 for unlimited attempts
45004GATEWAY_FAILEDNon-fatalShow reconnecting UI; SDK auto-reconnects by default. If autoReconnect: false, call client.connect() manually. Wait for telnyx.readyGateway reported FAILED / FAIL_WAIT / TIMEOUT; SDK retries until RECONNECTION_EXHAUSTED when autoReconnect is not disabled
autoReconnect is enabled by default. Unless you explicitly set autoReconnect: false, the SDK handles reconnection automatically for WEBSOCKET_ERROR, GATEWAY_FAILED, and signaling-health recovery. You only need to call client.connect() manually if you disabled autoReconnect or after RECONNECTION_EXHAUSTED.

Authentication and session errors

CodeNameFatal?Customer actionSDK behavior
46001LOGIN_FAILEDFatalRe-authenticate using client.login() without recreating the instanceRegistration never reached ready state
46002INVALID_CREDENTIALSFatalFix credential parameters; re-authenticate using client.login()Login was rejected before request was sent
46003AUTHENTICATION_REQUIREDFatal when autoReconnect: false; otherwise non-fatalRe-authenticate using client.login({ creds: { login_token: newToken } }) without recreating the instanceRequest was sent before auth completed or after auth was lost. SDK re-auths on reconnect when autoReconnect is enabled
48001NETWORK_OFFLINENon-fatalRestore network connectivity; the SDK recovers via health signals, not the browser online event aloneBrowser offline event fired. Emitted for backward compatibility; recovery is driven by SDK-owned health signals
48501SESSION_NOT_REATTACHEDFatalTerminate the local call and notify the user; start a new callThe WebSocket reconnected but the server did not reattach the call; subsequent call-control fails with CALL_DOES_NOT_EXIST
49001UNEXPECTED_ERRORFatalCheck event.error.originalError for details; report if it persistsUnclassified failure during peer/call setup
Re-authenticate without recreating the instance. For LOGIN_FAILED (46001), INVALID_CREDENTIALS (46002), and AUTHENTICATION_REQUIRED (46003), use client.login() to re-authenticate on the existing connection:
// Refresh token without recreating TelnyxRTC
await client.login({ creds: { login_token: newToken } });

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

import { SwEvent, TELNYX_WARNING_CODES } from '@telnyx/webrtc';

Warning event payload structure

Every warning event includes a structured warning 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:
interface ITelnyxWarningEvent {
  warning: ITelnyxWarning; // structured warning (plain object, not an Error)
  sessionId: string;
  callId?: string;
}

// Added on recovery warnings:
//   reason?: string                                        // human-readable reason
//   source?: 'probe' | 'request' | 'peer_failure' | 'no_rtp' // SIGNALING_RECOVERY_REQUIRED only
The structured warning object (ITelnyxWarning) exposes:
FieldTypeMeaning
codenumberNumeric warning code (e.g. 33004). Use this for branching.
namestringMachine-readable name in UPPER_SNAKE_CASE.
messagestringShort human-readable message for UI alerts.
descriptionstringFull explanation of the warning.
causesstring[]Possible root causes.
solutionsstring[]Suggested remediation steps.
Use 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

import { SwEvent, TELNYX_WARNING_CODES } from '@telnyx/webrtc';

client.on(SwEvent.Warning, (event) => {
  const { warning, sessionId, callId, reason, source } = event;

  switch (warning.code) {
    case TELNYX_WARNING_CODES.TOKEN_EXPIRING_SOON:
      refreshToken();
      break;

    case TELNYX_WARNING_CODES.PEER_CONNECTION_FAILED:
      showWarningBanner('Call is reconnecting');
      break;

    case TELNYX_WARNING_CODES.SIGNALING_RECOVERY_REQUIRED:
      // source tells you what triggered recovery: 'probe' | 'request' | 'peer_failure' | 'no_rtp'
      showReconnectingBanner(`Signaling recovery (${source})`);
      break;

    case TELNYX_WARNING_CODES.MEDIA_RECOVERY_REQUIRED:
      showReconnectingBanner('Media reconnecting');
      break;

    case TELNYX_WARNING_CODES.MULTIPLE_ACTIVE_CALLS_DETECTED:
      logMultiCall(callId, sessionId);
      break;

    case TELNYX_WARNING_CODES.RECONNECTION_FAILED_WITH_NO_AUTO_RECONNECT:
      showReconnectButton();
      break;

    default:
      console.warn(`[${warning.code}] ${warning.name}: ${warning.message}`);
  }
});

Warning code reference

Network quality warnings

CodeNameAuto-recovered?Customer action
31001HIGH_RTTMay self-resolveShow quality indicator; no immediate action needed
31002HIGH_JITTERMay self-resolveShow quality indicator; no immediate action needed
31003HIGH_PACKET_LOSSMay self-resolveShow quality indicator; no immediate action needed
31004LOW_MOSMay self-resolveShow quality indicator; consider advising user
31005LOW_LOCAL_AUDIOMay self-resolveShow microphone-level indicator; ask the user to check mute/input gain or selected microphone
31006LOW_INBOUND_AUDIOMay self-resolveVerify 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

CodeNameAuto-recovered?Customer action
32001LOW_BYTES_RECEIVEDMay self-resolve on reconnectCheck remote party; show degraded audio indicator
32002LOW_BYTES_SENTMay self-resolve on reconnectCheck local microphone; show degraded audio indicator

Call connection warnings

CodeNameAuto-recovered?Customer action
33001ICE_CONNECTIVITY_LOSTSDK attempts ICE reconnectShow reconnecting indicator; wait for recovery or PEER_CONNECTION_FAILED
33002ICE_GATHERING_TIMEOUTMay self-resolveCheck firewall/STUN/TURN config; show warning
33003ICE_GATHERING_EMPTYNoCheck network/firewall settings; STUN/TURN may be blocked
⚠️ 33004PEER_CONNECTION_FAILEDMay recover — SDK may attempt ICE restartShow reconnecting/degraded UI; wait for SDK recovery; only clean up after call termination
⚠️ 33005ONLY_HOST_ICE_CANDIDATESNoCheck STUN/TURN config; call may work on local network only
⚠️ 33006ANSWER_WHILE_PEER_ACTIVENoEnsure 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
33008ICE_CANDIDATE_PAIR_CHANGEDUsually yesLog candidate path changes and monitor quality; frequent changes indicate unstable network, VPN, NAT rebinding, or relay fallback
33009AUDIO_INPUT_DEVICE_CHANGE_SKIPPEDNoRetry after the call is active and local media is attached; verify the call was started with audio enabled
⚠️ 33010MULTIPLE_ACTIVE_CALLS_DETECTEDNo (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
⚠️ 33007DUPLICATE_INBOUND_ANSWERNoKeep a single active TelnyxRTC instance for inbound calls; disconnect old clients before replacing them; answer only one duplicate inbound notification

Authentication and session warnings

CodeNameAuto-recovered?Customer action
34001TOKEN_EXPIRING_SOONNo, but preventableRefresh the token before it expires; you have ~120 seconds
⚠️ 35002UNKNOWN_REATTACHED_SESSIONNoInspect the Attach callId in the warning payload; if a call should be active, start a new call manually

Signaling health and recovery warnings

CodeNameAuto-recovered?Customer action
⚠️ 36003SIGNALING_RECOVERY_REQUIREDSDK reconnects signaling and reattaches active callsShow short interruption state; wait for call reattach or call termination. source indicates the trigger: probe, request, peer_failure, or no_rtp
⚠️ 36004MEDIA_RECOVERY_REQUIREDSDK attempts ICE restart without reconnecting the socketShow media reconnecting indicator; keep call UI active until recovery or call termination
⚠️ 36005RECONNECTION_FAILED_WITH_NO_AUTO_RECONNECTNo — auto-reconnect is disabledThis 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).
Signaling health warnings are emitted when the SDK detects a half-dead WebSocket during an active call — the browser still reports the socket as 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.
Recovery is driven by SDK-owned health signals (liveness probe timeout, critical request timeout, peer failure, no-RTP). Browser 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 reaches hangup, inspect these fields on the Call object:
FieldTypeMeaning
causestring | nullHigh-level cause (USER_BUSY, CALL_REJECTED, etc.)
causeCodenumber | nullNumeric cause code
sipCodenumber | nullSIP response code when available
sipReasonstring | nullSIP reason phrase when available
Common causes:
CauseMeaning
NORMAL_CLEARINGExpected call completion
USER_BUSYRemote party was busy
CALL_REJECTEDRemote party rejected the call
NO_ANSWERCall timed out unanswered
UNALLOCATED_NUMBERDialed 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:
CodeMeaning
1000Normal closure
1001Going away
1006Abnormal closure
1011Internal 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 on client.connection:
GetterMeaning
client.connection.connectedWebSocket is in OPEN
client.connection.isAliveCONNECTING or OPEN
client.connection.isDeadCLOSING or CLOSED
Example:
const placeCall = (destinationNumber) => {
  if (!client.connection.connected) {
    showErrorMessage('Still connecting to Telnyx. Please try again shortly.');
    return;
  }

  client.newCall({ destinationNumber });
};

Reconnection Behavior

On telnyx.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

Browser online/offline events are treated as low-confidence hints, not direct recovery triggers:
  • offline emits the NETWORK_OFFLINE (48001) error for backward compatibility/telemetry and may accelerate a signaling health probe. It does not force a reconnect.
  • online clears 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 both SocketError 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 until RECONNECTION_EXHAUSTED (45003).

Keeping media alive

If keepConnectionAliveOnSocketClose 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 same b2bua-rtc instance. To break this stickiness and route to a different instance:
// Before reconnecting
client.clearReconnectToken();

// Or configure the SDK to skip the last voice SDK ID on reconnect
const client = new TelnyxRTC({
  login_token: jwt,
  skipLastVoiceSdkId: true,
});
Note: clearReconnectToken() and skipLastVoiceSdkId are 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

Featurev2.25.25v2.26.0+
Structured error codesNot availableTELNYX_ERROR_CODES with numeric codes
telnyx.warning eventNot availableAvailable with TELNYX_WARNING_CODES
TelnyxError classNot availableStructured error class with .code, .name, .message
isMediaRecoveryErrorEvent()Not availableAvailable for media permission recovery
SDK_ERRORS / SDK_WARNINGSNot availableAvailable for error/warning metadata
Reconnect backoffFixed/random 2-6s delayExponential backoff with jitter, capped at 30s
Browser online/offlineDirectly drives reconnectLow-confidence hints; recovery driven by SDK health signals
Primary error surfacetelnyx.error (raw Error) + telnyx.notificationtelnyx.error (structured) + telnyx.warning

Error events in v2.25.25

In v2.25.25, errors are emitted through telnyx.error and telnyx.notification: telnyx.error — Session-level errors with raw Error objects (no .code property):
client.on(SwEvent.Error, (event) => {
  // event.error is a plain Error object — no structured code
  // event.type may include ERROR_TYPE.invalidCredentialsOptions
  // event.sessionId is available
  console.error('SDK error:', event.error?.message || event.error);
});
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:
client.on(SwEvent.Notification, (notification) => {
  switch (notification.type) {
    case 'userMediaError':
      // notification.error — raw browser Error/DOMException
      // notification.errorName — error name string
      // notification.errorMessage — error message string
      // notification.call — the Call object (if available)
      // SDK automatically hangs up the call
      showPermissionPrompt(notification.errorMessage);
      break;

    case 'peerConnectionFailureError':
      // notification.error — raw error
      // Peer connection failed, but the call may be recovered by the server
      // via attach with 'recovering' state. Show degraded UI and wait.
      showReconnectingBanner();
      break;

    case 'signalingStateClosed':
      // Peer signaling state closed — peer is not recoverable
      // But the call may still recover through server attach
      // Only clean up after a final hangup/callUpdate confirms loss
      showReconnectingBanner();
      break;

    case 'callUpdate':
      // Normal call lifecycle
      handleCallUpdate(notification.call);
      break;
  }
});
Error-related notification types:
notification.typeMeaningFatal?Customer action
userMediaErrorMedia device access failedYes (for the call)Prompt user for microphone permission; the SDK hangs up the call automatically
peerConnectionFailureErrorPeer connection failedPeer connection not recoverable, but call may be recoveredShow 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
signalingStateClosedPeer signaling state closedPeer connection not recoverable, but call may still be recovered by serverShow 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 on telnyx.error with a type field for invalid credentials. You can re-authenticate using client.login() without recreating the TelnyxRTC instance:
import { SwEvent, ERROR_TYPE } from '@telnyx/webrtc';

client.on(SwEvent.Error, (event) => {
  if (event.type === ERROR_TYPE.invalidCredentialsOptions) {
    // Credentials were invalid before the request was sent
    showLoginError('Please check your credentials.');
    return;
  }

  // For LOGIN_FAILED or AUTHENTICATION_REQUIRED, re-login without recreating the instance:
  // await client.login({ creds: { login_token: newToken } });

  // Other errors — generic handling
  showErrorMessage(event.error?.message || 'An error occurred');
});

Reconnection in v2.25.25

Reconnection behavior differs from the current version:
  • autoReconnect is enabled by default; the SDK automatically reconnects unless you set autoReconnect: false
  • Reconnect uses a fixed/random 2-6 second delay (current SDK uses exponential backoff with jitter, capped at 30s)
  • Browser online/offline events directly drive reconnect (current SDK treats them as low-confidence hints)
  • No maxReconnectAttempts option (current SDK defaults to 10 attempts and supports maxReconnectAttempts: 0 for unlimited attempts)
  • No clearReconnectToken() method
  • No skipLastVoiceSdkId option
  • keepConnectionAliveOnSocketClose is available

Migrating from v2.25.25 to the latest

If you are upgrading from v2.25.25 to the latest version:
  1. Replace telnyx.notification error handling — use telnyx.error for fatal errors and telnyx.warning for non-fatal conditions. Keep telnyx.notification for call lifecycle only.
  2. Replace notification.type === 'userMediaError' handling with telnyx.error listener switching on event.error.code (42001, 42002, 42003).
  3. Replace notification.type === 'peerConnectionFailureError' handling with telnyx.warning listener for PEER_CONNECTION_FAILED (33004).
  4. Replace notification.type === 'signalingStateClosed' handling with telnyx.warning listener for the appropriate warning code.
  5. Replace ERROR_TYPE.invalidCredentialsOptions checks with event.error.code === TELNYX_ERROR_CODES.INVALID_CREDENTIALS (46002). Use client.login() to re-authenticate without recreating the TelnyxRTC instance.
  6. Import new symbols: TelnyxError, TELNYX_ERROR_CODES, TELNYX_WARNING_CODES, isMediaRecoveryErrorEvent.
  7. Note SESSION_NOT_REATTACHED is an error, not a warning: in v2.26.0+ it is TELNYX_ERROR_CODES.SESSION_NOT_REATTACHED (48501) on telnyx.error (fatal), not a warning. UNKNOWN_REATTACHED_SESSION (35002) is the separate warning.