Rate this page:

Migrating to Voice JavaScript SDK 2.0

You can view details of SDK updates in You’ll find full API documentation in the GitHub pages site.

In this guide, you’ll see references to a Call class. This is the renamed Connection class. You’ll find more details in the guide below.

Use the new NPM module instead of CDN

The Voice JavaScript SDK is published under @twilio/voice-sdk. The 1.x SDKs will still be available under twilio-client.

In the interest of promoting best practices, 2.0 and newer releases will not be uploaded to the Twilio CDN. Existing versions (prior to 2.0) and any further 1.x releases will continue to be served via the CDN. However, we still strongly recommend not to link directly to these files in production as it introduces the potential for interruption due to CDN outages.

For customers currently using the CDN, there are a couple of options for switching:

  1. If your project uses NPM, install the latest version of @twilio/voice-sdk. From here, the built distribution files will be available under node_modules/@twilio/voice-sdk/dist.
  2. If your project does not use NPM, it’s possible to download the latest built artifact and serve it from your own servers. Each release will have its own tag here. Download the latest release, unzip it, and navigate to /dist for the twilio.js and twilio.min.js files.

Stop using the singleton; instantiate the Device

The singleton behavior of Device has been removed in favor of best practices. Device.setup() will no longer work; instead, a new Device must be instantiated via new Device(token, options?).

Stop using device.setup and instead use device.updateOptions

As noted above, Device.setup will no longer work for creating a new device. Additionally, device.setup will no longer work for updating the device after it has been created. Instead, use device.updateOptions(options) to update any of the Device's configured options after creating the Device.

Stop listening for Device.on(‘ready’)

The Device is now lazy about opening the signaling connection, so new Device(...) is synchronous and will not cause a signaling channel to be opened. The signaling WebSocket will be opened when either:

  • Device.register() is called, registering the client to receive incoming calls
  • Device.connect() is called, placing an outbound call. It is not necessary to be registered to place an outgoing call.

If your application needs to know when the Device successfully registers, unregisters, or begins registering, it can listen for:


For signaling connection status during an outbound call when not registered, you should monitor the Call status.

The full list of Device events is now:

enum Device.EventName {
  Error = 'error',
  Incoming = 'incoming',
  Unregistered = 'unregistered',
  Registering = 'registering',
  Registered = 'registered',
  Destroyed = 'destroyed',

Make sure the access token is kept up-to-date

Now that signaling is lazy, the access token must be kept updated with Device.updateToken(token) prior to registering or placing an outbound Call. In addition, an outdated token will cause sending statistics to Voice Insights to fail — this is an existing behavior from 1.x. In the future, as we explore signaling reconnection support, keeping this token updated will be even more important in order to automatically restore connection to a Call or automatically re-register the endpoint.

This can look something like:

const ttl = 600000; // 10 minutes
const refreshBuffer = 30000; // 30 seconds

const token = await getTokenViaAjax({ ttl });
const device = new Device(token);

setInterval(async () => {
  const newToken = await getTokenViaAjax({ ttl });
}, ttl - refreshBuffer); // Gives us a generous 30-second buffer

Update references to Device state

The Device states have changed to:

namespace Device {
  isBusy: boolean;
  enum State {
    Unregistered = 'unregistered';
    Registering = 'registering';
    Registered = 'registered';
    Destroyed = 'destroyed';

We no longer represent busy state as part of the Device state; we’ve moved it to the device.isBusy boolean. The ready state is now Device.Registered, and the offline event is now Device.Unregistered. An important note here is that ready and offline have always indicated registration status rather than signaling connection status, which was confusing and should be much clearer with the new names.

Update calls to Device.connect() and Call.accept()

The arguments for Device.connect() and Call.accept() have been standardized to the following options objects:

interface Call.AcceptOptions {
   * An RTCConfiguration to pass to the RTCPeerConnection constructor.
  rtcConfiguration?: RTCConfiguration;
   * MediaStreamConstraints to pass to getUserMedia when making or accepting a Call.
  rtcConstraints?: MediaStreamConstraints;
interface Device.ConnectOptions extends Call.AcceptOptions {
  * A flat object containing key:value pairs to be sent to the TwiML app.
  params?: Record<string, string>;

Note that these now take MediaStreamConstraints. For example:

device.connect({ To: 'client:alice' }, { deviceId: 'default' });

might be re-written as:

  params: { To: 'client:alice' },
  rtcConstraints: { audio: { deviceId: 'default' } },

Update error handlers

For backward compatibility, the new error format was attached to the old format under error.twilioError:

class oldError extends Error {
  code: number;
  message: string;
  twilioError: TwilioError;

The new Error format is:

class TwilioError extends Error {
   * A list of possible causes for the Error.
  causes: string[];
   * The numerical code associated with this Error.
  code: number;
   * A description of what the Error means.
  description: string;
   * An explanation of when the Error may be observed.
  explanation: string;
   * Any further information discovered and passed along at run-time.
  message: string;
   * The name of this Error.
  name: string;
   * The original Error received from the external system, if any.
  originalError?: Error;
   * A list of potential solutions for the Error.
  solutions: string[];

If you were already using the new format, the migration should be straightforward:

// changing...
device.on('error', e => {

// to...
device.on('error', e => {

Otherwise, some of the error codes and messages may have changed from the old platform-specific codes to the new codes which are more consistent with Twilio, our REST API, and other SDKs.

Update outdated error codes

With the Error transition, the following error codes have changed:

  • 31003 -> 53405 | When ICE connection fails
  • 31201 -> 31402 | When getting user media fails
  • 31208 -> 31401 | When user denies access to user media
  • 31901 -> 53000 | When the websocket times out in preflight

Update Call logic to include ringing states if needed

In a prior patch, we included ringing states on Call, which were enabled behind the Device options flag enableRingingState. We have removed this flag in 2.0, as it is now on by default. For full functionality, this still requires setting answerOnBridge in the TwiML application. However, even without answerOnBridge, the Call will go through the new ringing state (briefly, for a few ms) before getting to open. When answerOnBridge is enabled, the ringing state will begin when the recipient’s phone begins ringing and transition to open when they have answered and media is established.

This shouldn’t affect most applications, however it may be necessary for your application to know about the Call.Ringing state and ringing event, depending on your implementation.

Update deprecated (now removed) methods and options

call.mediaStream was removed. Use call.getRemoteStream() and call.getLocalStream() to acquire the active media streams.

call.message was removed. call.customParameters holds the same information in a Map

The following fields/methods were meant to be private, so were removed from the public interface:

  • call.options
  • call.pstream
  • call.sendHangup()

call.on('cancel') will no longer fire in response to a local call.ignore(). Instead, it will only fire when the remote end has canceled.

The following Device options have been removed, as they are now default to true:

  • enableIceRestart
  • enableRingingState
  • fakeLocalDtmf

Update your Device.Options

The new Device.Options interface is:

interface Device.Options {
  allowIncomingWhileBusy?: boolean;
  appName?: string;
  appVersion?: string;
  audioConstraints?: MediaTrackConstraints | boolean;
  closeProtection?: boolean | string;
  codecPreferences?: Connection.Codec[];
  disableAudioContextSounds?: boolean;
  dscp?: boolean;
  edge?: string[] | string;
  forceAggressiveIceNomination?: boolean;
  maxAverageBitrate?: number;
  rtcConfiguration?: RTCConfiguration;
  sounds?: Partial<Record<Device.SoundName, string>>;

(Optional) Rename Connection to Call

We’ve renamed the Connection class to Call. This shouldn’t affect any public API; however, some internal method names have been updated. Therefore, any code reaching into the internals will break if not updated.

Remove Device.activeCall

As part of 2.0.1 (GA Release) we’ve also removed Device.activeCall, instead the application should maintain references to calls accepted from the event Device.on(‘incoming’) or made using Device.connect.

Rate this page:

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd by visiting Twilio's Community Forums or browsing the Twilio tag on Stack Overflow.


        Thank you for your feedback!

        We are always striving to improve our documentation quality, and your feedback is valuable to us. Please select the reason(s) for your feedback or provide additional information about how we can improve:

        Sending your feedback...
        🎉 Thank you for your feedback!
        Something went wrong. Please try again.

        Thanks for your feedback!

        Refer us and get $10 in 3 simple steps!

        Step 1

        Get link

        Get a free personal referral link here

        Step 2

        Give $10

        Your user signs up and upgrade using link

        Step 3

        Get $10

        1,250 free SMSes
        OR 1,000 free voice mins
        OR 12,000 chats
        OR more