> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# WebRTC JS SDK error handling

> Handle errors, warnings, and connection failures with the Telnyx WebRTC JS SDK, including fatal errors, recommended recovery actions, reconnection behavior, and version-specific guidance.

# 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              |

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:

| 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`. |

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](#reconnection-behavior)) ship in the next release. If you are on v2.25.25, see [Error handling in v2.25.25](#error-handling-in-v22525) 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

```javascript theme={null}
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):

```typescript theme={null}
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):

```typescript theme={null}
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:

| 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`. |

> **`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

```javascript theme={null}
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:

```javascript theme={null}
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 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 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.

| 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.** `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 (`42001`–`42003`) 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:
>
> ```javascript theme={null}
> // 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`):**

| 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                                       |

**Important warnings (handle on `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 |

> **`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

| 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.** For `LOGIN_FAILED` (`46001`), `INVALID_CREDENTIALS` (`46002`), and `AUTHENTICATION_REQUIRED` (`46003`), use `client.login()` to re-authenticate on the existing connection:
>
> ```javascript theme={null}
> // 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

```javascript theme={null}
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:

```typescript theme={null}
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:

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

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

```javascript theme={null}
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

| 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). |

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:

| 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                      |

Common causes:

| 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 on `client.connection`:

| Getter                        | Meaning                |
| ----------------------------- | ---------------------- |
| `client.connection.connected` | WebSocket is in `OPEN` |
| `client.connection.isAlive`   | `CONNECTING` or `OPEN` |
| `client.connection.isDead`    | `CLOSING` or `CLOSED`  |

Example:

```javascript theme={null}
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:

```javascript theme={null}
// 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

| 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 through `telnyx.error` and `telnyx.notification`:

**`telnyx.error`** — Session-level errors with raw `Error` objects (no `.code` property):

```javascript theme={null}
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:

```javascript theme={null}
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.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 on `telnyx.error` with a `type` field for invalid credentials. You can re-authenticate using `client.login()` without recreating the `TelnyxRTC` instance:

```javascript theme={null}
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.
