LLOQU
ProductPricingBlogDocsLogin

On this page

Why does my Twilio Device go offline?Token expiry: the one-TTL rule"It never registers": v2 does not register automaticallyThe WebSocket never openedCheck your SDK version before you debug anything elseMigrating from v1: "Twilio.Device is not a constructor"A five-minute diagnosticFAQWho keeps the registration alive
All articles
TwilioVoice SDKErrors

Twilio Device Offline: Why It Happens and How to Fix It

A Twilio Device goes offline because its token lapsed or because register() was never called. How to tell which one you have, and the fix for each.

Alloqui TeamAug 13, 202610 min read
On this page
Why does my Twilio Device go offline?Token expiry: the one-TTL rule"It never registers": v2 does not register automaticallyThe WebSocket never openedCheck your SDK version before you debug anything elseMigrating from v1: "Twilio.Device is not a constructor"A five-minute diagnosticFAQWho keeps the registration alive
Back to all articles
LLOQUAlloqui

Product

  • Features
  • Pricing
  • Blog

Developers

  • Documentation
  • @alloqui/dialer on npm

Company

  • Contact

Legal

  • Privacy
  • Terms

© 2026 Alloqui

A Twilio Device is offline when it has no registered signaling connection to Twilio — either it never registered, or its registration lapsed. In the Voice JavaScript SDK v2 that reads as device.state === 'unregistered' and device.edge === null. Four causes cover nearly every report: an access token that expired, a device.register() call that was never made, a WebSocket the network blocked, and an SDK old enough to still carry the reconnection bugs Twilio fixed in January 2024. When the failure happens tells you which one you have.

Why does my Twilio Device go offline?

The Device tracks one thing: whether Twilio has a live registration for your identity. It starts in unregistered, moves to registering when you call device.register(), and lands on registered once Twilio confirms it. Anything that kills the signaling WebSocket — an expired token, a dropped network, a proxy that blocks wss — drops it back down, and the SDK emits unregistered.

Timing is the diagnostic. A Device that never comes up at all has a registration or transport problem. A Device that works fine and then dies at a suspiciously round interval has a token problem.

What you seeMost likely causeWhere to look
Offline immediately, never registersdevice.register() never calledYour init code — v2 does not register for you
Offline exactly one TTL after page load (an hour, by default)Access token expiredtokenWillExpire handler, tokenRefreshMs
Offline only for some users or networksWebSocket blocked by firewall or proxyNetwork egress rules, port 443 wss
Offline after a laptop sleep or Wi-Fi flapSignaling reconnect stalledYour SDK version — check it's ≥ 2.10.1
Registered, then offline after a browser back/forward navigationChrome BFCache destroying the DeviceSDK version, Chrome 149+

Token expiry: the one-TTL rule

This is the single most common cause, and it has a signature you can't miss: the Device works perfectly, then goes offline at almost exactly the same elapsed time after every page load. Twilio access tokens carry a TTL of up to 24 hours, and most quickstarts ship 3600 seconds. When the token dies, the registration dies with it, and unregistered fires — Twilio's own best-practices doc names token expiry as one of the two reasons that event is emitted.

The SDK gives you an early warning. Pass a tokenRefreshMs option and the Device schedules a tokenWillExpire event that many milliseconds before the token's TTL runs out. Handle it by fetching a fresh token and pushing it in with device.updateToken():

import { Device } from '@twilio/voice-sdk';

const device = new Device(token, {
  tokenRefreshMs: 60000, // 60s of headroom — the default is only 10s
});

device.on('registered',   () => setStatus('ready'));
device.on('unregistered', () => setStatus('offline'));

device.on('tokenWillExpire', async () => {
  const { token: fresh } = await fetch('/api/voice-token').then(r => r.json());
  device.updateToken(fresh);
});

device.on('error', (e) => {
  console.error(e.code, e.message); // 31205 = JWT expired, past the point of refresh
});

await device.register();

Two things about that default are worth changing. tokenRefreshMs defaults to 10000 — ten seconds of warning, verified in lib/twilio/device.ts in the SDK source. If your token endpoint is slow, or the user's connection stalls for fifteen seconds, the refresh loses the race and the Device drops. Give yourself thirty to sixty seconds instead.

The second is subtler and bites production apps. The SDK arms that warning with a plain setTimeout, and it arms it from the TTL that Twilio sends on the signaling connected event. Browsers throttle timers in backgrounded tabs aggressively — Chrome drops hidden tabs to roughly one timer wake per minute, and harder after five minutes. A dialer sitting in a background tab can therefore miss its own refresh window and wake up unregistered. If your agents keep the dialer in a background tab, drive the refresh from a source the browser doesn't throttle, or re-check token validity on visibilitychange and re-register when the tab comes back.

Note also what the timer implies: it's only set once signaling has connected. A Device constructed with an already-dead token never gets a refresh timer at all, because it never reaches connected. That is a separate failure from expiry mid-session, and it usually shows up as error 31205 or a JWT validation failure rather than a clean unregistered.

"It never registers": v2 does not register automatically

The most common migration trap in the Voice SDK, and the reason so many "device is offline, no errors" reports have nothing wrong with them. In v1 you called Twilio.Device.setup(token) and the SDK opened signaling for you, then fired ready. In v2 the signaling connection is lazy. Constructing the Device does nothing over the network:

const device = new Device(token); // synchronous, no socket, still 'unregistered'

Twilio's 2.0 release notes are explicit about it: Device.setup() was removed, new Device(...) will not begin connecting to signaling, and the socket opens in exactly two situations — you call device.connect() to place an outbound call, or you call device.register() to listen for incoming ones. Skip register() and your Device sits in unregistered forever, looking broken while behaving exactly as designed.

The reason this keeps happening is that the internet is full of v1 tutorials. Search results, Medium posts, and a good share of the AI-generated answers on this topic still show Twilio.Device.setup() and an on('ready') handler, none of which exist in v2. If the code you're copying mentions ready or offline events, it's v1 code.

Two lines confirm which situation you're in, and they cost nothing to log:

console.log(device.state); // 'unregistered' | 'registering' | 'registered' | 'destroyed'
console.log(device.edge);  // null when the Device is not connected to an edge

A state of unregistered with no error event at all means nothing ever tried. A state that reaches registering and falls back means the attempt failed, and the error event carries the code.

The WebSocket never opened

If registration fails on some networks and works on others, the signaling socket is being blocked before it can be established — a corporate firewall or proxy dropping outbound wss traffic on port 443. That produces error 53000, signaling connection error, which has its own diagnosis path and its own network requirements to hand your IT team.

Check your SDK version before you debug anything else

Three of the best-known "device offline" reports on GitHub are bugs Twilio has already fixed. If you're on an older @twilio/voice-sdk, you may be debugging a problem that a version bump removes.

Updating the token after signaling died (issue #33). Register the Device, kill the network so signaling drops, bring it back, then call updateToken(). The token update triggers a signaling connect, the SDK's internal re-register flag is still set, and register() runs against an already-registered Device — throwing Attempt to register when device is in state "registered". Must be "unregistered". The Device is then wedged, and recreating it is the only reliable way out. Fixed in 2.10.0 (5 January 2024).

register() that never settles (issue #231). Construct a Device with an expired token and await device.register(). The WebSocket closes because the token is bad, but the promise neither resolves nor rejects — so every line after the await never runs, and the UI hangs on "connecting" with no error to show. Fixed in 2.10.1 (12 January 2024).

// Until you're on 2.10.1+, don't trust register() to come back
await Promise.race([
  device.register(),
  new Promise((_, rej) => setTimeout(() => rej(new Error('register timeout')), 10000)),
]);

Chrome's back/forward cache (issue #446). This one is still live as of August 2026. On Chrome 149 and later, navigating away and back restores the page from the BFCache — and the Device was permanently destroyed in the process, so the dialer comes back dead with no way to recover short of a reload. The fix is landed in the SDK's changelog under 2.18.4, which has not shipped: the latest published release is 2.18.3 (11 May 2026). Until it does, treat a BFCache restore as a signal to tear down and rebuild the Device. When 2.18.4 lands, the Device will close signaling on cache and re-register on restore, which means your UI will flip to unregistered and back on every such navigation — plan the status indicator accordingly.

Once you're on a current version, the SDK does try to recover signaling on its own. It reconnects with backoff, and since 2.18.0 (5 January 2026) it honours a server-provided Retry-After when an attempt is rejected. On a successful signaling reconnect, a Device that had been registered re-registers itself. What the SDK will not do is notice that the machine changed networks, or that a sleeping laptop woke up — that's still yours to wire, via online and visibilitychange listeners that check device.state and call register() when it comes back unregistered.

Migrating from v1: "Twilio.Device is not a constructor"

If you hit TypeError: Twilio.Device is not a constructor, you're mixing v1 and v2. In v1 (twilio-client, loaded from a CDN) Twilio.Device was a global singleton with static methods, so there was nothing to construct. In v2 (@twilio/voice-sdk) Device is a class you import and instantiate:

// v1 — twilio-client, CDN global, EOL
Twilio.Device.setup(token);
Twilio.Device.on('ready',   () => {});
Twilio.Device.on('offline', () => {});

// v2 — @twilio/voice-sdk
import { Device } from '@twilio/voice-sdk';
const device = new Device(token);
device.on('registered',   () => {});
device.on('unregistered', () => {});
await device.register();

The rename is the part that matters for this page, because the v1 vocabulary is where "offline" comes from in the first place:

v1 (twilio-client)v2 (@twilio/voice-sdk)Note
Twilio.Device.setup(token)new Device(token) and await device.register()Two steps now; the constructor opens no socket
Device.on('ready')device.on('registered')Plus a new registering event while it's in flight
Device.on('offline')device.on('unregistered')Fires on token expiry and on lost signaling
ConnectionCallRenamed for consistency with the mobile SDKs
Device.activeConnectionkeep your own referenceDevice.activeCall was removed

Twilio renamed ready/offline because they never described the connection — they described registration, which is why "my device is offline" and "my device isn't registered" turn out to be the same bug wearing two names.

Version 1.x reached end of life on 10 September 2025 and is no longer maintained or supported. That has not stopped anyone: twilio-client still pulled 22,938 npm downloads in the week ending 9 August 2026, against 928,695 for @twilio/voice-sdk. Roughly one in forty installs is still on a package that stopped receiving fixes almost a year ago. If that's you, the migration is a few hours and it retires an entire category of offline bug.

A five-minute diagnostic

  1. Log device.state and device.edge right after init — unregistered with a null edge and no error means register() was never called.
  2. Check @twilio/voice-sdk in your package.json. Below 2.10.1, upgrade before debugging further.
  3. Time the failure. Offline at roughly one TTL after load is a token problem; offline from the first second is registration or transport.
  4. Decode the JWT and read its exp. Compare against when the Device dropped.
  5. Confirm a tokenWillExpire handler exists and actually fires — log inside it, and raise tokenRefreshMs above the default 10,000.
  6. Reproduce on a different network. If it only fails on the corporate one, you're looking at a blocked WebSocket, not a token.
  7. Watch for 31005 or a bare 31000 in the error handler — both can accompany a Device that drops mid-session.

FAQ

Do I need to call device.register() to make outgoing calls? No. device.connect() opens the signaling connection on its own, so an unregistered Device can still dial out. register() exists to receive incoming calls at the identity in your access token. A Device showing unregistered while outbound calls work is behaving normally.

What's the difference between unregistered and destroyed? An unregistered Device can be brought back with device.register(). A destroyed one cannot — device.destroy() makes the instance permanently unusable, and every later register() rejects. A dialer that refuses to reconnect no matter what you call is usually a Device someone already destroyed.

Is my Twilio Device offline because of a Twilio outage? Rarely, but it takes ten seconds to rule out at status.twilio.com. A real outage affects every user at once. Failures limited to one user, one network, or one browser tab are configuration, token, or transport problems on your side.

What does "could not connect to Twilio's servers" mean? It's the v1 phrasing for a signaling connection that never established — the same condition v2 reports through the error event with a specific code. Seeing that exact string means you're on twilio-client 1.x, which reached end of life on 10 September 2025.

Does the Twilio SDK re-register automatically after the network comes back? It re-establishes signaling with backoff, and a Device that was registered before the drop re-registers when signaling reconnects. It does not detect network changes or wake-from-sleep, so add online and visibilitychange listeners that check device.state and call register() if it reads unregistered.


Who keeps the registration alive

Registration lifecycle and token refresh are the two things a browser dialer has to get right forever, and they're the two we own. Alloqui is a <Dialer /> component that runs on your own Twilio keys: our engine calls device.register() at init and wires the registered/unregistered/error events, and our token manager refreshes proactively through device.updateToken() before the TTL runs out, with backoff on failure. SDK version bumps — including the fixes above — become our maintenance, not yours.