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
| Code | Meaning | What to do |
|---|---|---|
TOKEN_INVALID | The JWT is malformed or signed with the wrong key. | Mint a fresh token via your backend. |
TOKEN_EXPIRED | The JWT is past its exp. Usually means you missed the tokenWillExpire event. | Call device.updateToken(fresh). |
AUTH_ERROR | The SIP server rejected the credentials (typically a 401/403). | Mint a fresh token; if it persists, the account or user may be disabled. |
Lifecycle
| Code | Meaning | What to do |
|---|---|---|
CONFIG_FETCH_FAILED | Failed 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_FAILED | SIP REGISTER did not succeed. | Surface to user, try device.register() again after a backoff. |
REGISTER_ERROR | Lower-level UA failure during REGISTER. | Same as above; inspect cause for the JsSIP detail. |
UNREGISTER_ERROR | device.unregister() threw internally. Non-fatal — the WebSocket still closes. | Usually safe to ignore. |
NOT_REGISTERED | You called device.connect() before register() resolved (or after unregister()). | Wait for the registered event before placing calls. |
DESTROYED | A method was called on a Device that has been destroy()-ed. | Create a new Device. |
Call control
| Code | Meaning | What to do |
|---|---|---|
CALL_FAILED | The call could not be placed or was terminated abnormally (SIP failure, ICE gathering failure, etc.). | Show the user; the disconnect event will follow. |
MUTE_FAILED | call.mute() failed at the WebRTC layer. | Usually a transient browser issue; retry. |
HOLD_FAILED | call.hold() failed (typically a re-INVITE rejection). | Retry; some carriers don't allow hold. |
DTMF_FAILED | call.sendDigits() threw — usually because the call is not yet in accepted state. | Wait for the accepted event before sending DTMF. |
MEDIA_ERROR | Audio 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.