Skip to main content

Errors

Every error the SDK raises is an instance of VoiceSDKError with a typed .code you can switch on. Fatal failures during await device.register() or await device.connect(...) reject the returned promise; everything else surfaces via the error event on the affected Device or Call.

import { VoiceSDKError, type VoiceSDKErrorCode } from "@2chat/voice-sdk";

device.on("error", (err: VoiceSDKError) => {
switch (err.code) {
case "TOKEN_EXPIRED":
case "AUTH_ERROR":
refreshToken();
break;
case "REGISTRATION_FAILED":
showOfflineBanner();
break;
}
});

Shape

class VoiceSDKError extends Error {
readonly code: VoiceSDKErrorCode;
readonly cause?: unknown; // the original error, when there is one

toJSON(): { name: string; code: string; message: string };
}

There are also three named subclasses for convenience: AuthError, RegistrationError, and TokenError. They are plain VoiceSDKErrors with a fixed .code.

Codes

Authentication & token

CodeMeaningWhat to do
TOKEN_INVALIDThe JWT is malformed or signed with the wrong key.Mint a fresh token via your backend.
TOKEN_EXPIREDThe JWT is past its exp. Usually means you missed the tokenWillExpire event.Call device.updateToken(fresh).
AUTH_ERRORThe SIP server rejected the credentials (typically a 401/403).Mint a fresh token; if it persists, the account or user may be disabled.

Lifecycle

CodeMeaningWhat to do
CONFIG_FETCH_FAILEDFailed to fetch SIP credentials from the 2Chat API.Check the browser's network connectivity. The cause field contains the underlying fetch error or HTTP status.
REGISTRATION_FAILEDSIP REGISTER did not succeed.Surface to user, try device.register() again after a backoff.
REGISTER_ERRORLower-level UA failure during REGISTER.Same as above; inspect cause for the JsSIP detail.
UNREGISTER_ERRORdevice.unregister() threw internally. Non-fatal — the WebSocket still closes.Usually safe to ignore.
NOT_REGISTEREDYou called device.connect() before register() resolved (or after unregister()).Wait for the registered event before placing calls.
DESTROYEDA method was called on a Device that has been destroy()-ed.Create a new Device.

Call control

CodeMeaningWhat to do
CALL_FAILEDThe call could not be placed or was terminated abnormally (SIP failure, ICE gathering failure, etc.).Show the user; the disconnect event will follow.
MUTE_FAILEDcall.mute() failed at the WebRTC layer.Usually a transient browser issue; retry.
HOLD_FAILEDcall.hold() failed (typically a re-INVITE rejection).Retry; some carriers don't allow hold.
DTMF_FAILEDcall.sendDigits() threw — usually because the call is not yet in accepted state.Wait for the accepted event before sending DTMF.
MEDIA_ERRORAudio device swap failed (setInputDevice / setOutputDevice).Re-enumerate devices; the user may have unplugged the headset.

Handling pattern

async function safeConnect(device, params) {
try {
return await device.connect(params);
} catch (err) {
if (!(err instanceof VoiceSDKError)) throw err;
switch (err.code) {
case "NOT_REGISTERED":
await device.register();
return device.connect(params);
case "AUTH_ERROR":
case "TOKEN_EXPIRED":
await refreshToken(device);
return device.connect(params);
default:
toast(`Call failed: ${err.message}`);
throw err;
}
}
}

Always instanceof-check before reading .code — third-party WebRTC errors can bubble up too.