This guide walks through moving an existing v3 (@signalwire/js@3.x) integration to v4. v3 was built around RoomSession and event emitters for video conferencing. v4 unifies calling and conferencing under a single Call API, replaces event emitters with RxJS observables, and introduces a credential-provider auth model with automatic token refresh.
v4 covers the bulk of v3, but some features are still in progress. Check this table before migrating.
If your application depends on recording, streaming, playback, room locking, metadata, or transfer, wait for these features to land before migrating.
await SignalWire({ token }) with new SignalWire(credentialProvider)rootElement from dial() and attach media streams manually (or use web components)node_id / userVariables / await call.start() — handled by v4 internallyRoomSession methods to Call / call.self equivalentsroomObj.on('event', ...) with call.eventName$.subscribe(...)invite.accept / invite.reject to call.answer() / call.reject()client.online() / client.offline() — registration is automatic (use client.unregister() to go offline)call.selfWebRTC.getCameras() etc. for client.videoInputDevices$MediaDeviceInfo objects (not bare deviceId) to device selectorsclient.address.getAddresses() with client.directory.addresses$client.conversation messaging with callAddress.sendText() / textMessages$call.hangup(), client.disconnect(), client.destroy()The package name is unchanged. Upgrade to the v4 major release:
For the browser build:
v4 ships as an ES module. If you bundled v3 as a CDN global, switch to module imports:
v3 accepted a room token directly. v4 introduces a CredentialProvider that owns the token lifecycle — including scheduled refresh before expiry. Use Subscriber Access Tokens (SAT) for authenticated users and Embed Tokens for guest access. See Authentication for the full reference.
The SDK ships with StaticCredentialProvider for pre-obtained tokens (build-time SAT, server-rendered pages). For long-running apps, implement a custom provider that fetches and refreshes a SAT from your backend.
When the SDK passes an AuthenticateContext with a DPoP key fingerprint, forward it to your token endpoint to request a Client Bound SAT with automatic refresh:
v3 was an async factory. v4 is a synchronous constructor; connection happens automatically when you subscribe to the first observable.
Before (v3):
After (v4):
By default, the client connects and registers automatically on construction. Pass skipConnection: true or skipRegister: true if you want to drive that lifecycle yourself.
v3 had no separate connection observable — the factory was the connect call. v4 exposes connection state reactively:
v3 took an options object with to, rootElement, optional nodeId for routing, and userVariables, then required await call.start(). v4 takes the destination as the first argument, handles steering internally, and does not auto-attach media — you wire streams up yourself.
Before (v3):
After (v4):
v3 used client.online({ incomingCallHandlers }) with callbacks and an explicit offline(). v4 registers the user automatically on construction and exposes incoming calls as an observable — no online/offline toggle. To go offline for inbound calls, call client.unregister() (and client.register() to come back online). answer() and reject() are synchronous in v4 — no await needed.
Before (v3):
After (v4):
v3 distinguished between CallFabricRoomSession and RoomSession. v4 collapses both into a single Call, with self-participant controls moved off the room object onto call.self.
call.self is a full participant object with reactive state.
Event emitters are gone — participants are an observable list. Each participant also exposes individual observables for granular updates.
Before (v3):
After (v4):
Screen sharing moves from the room to call.self.
Recording and streaming APIs are not yet implemented in v4. The observables exist for monitoring server-initiated state, but startRecording() and startStreaming() will throw. Drive these from SWML or the server-side REST API in the meantime.
The standalone WebRTC namespace and roomObj.updateCamera()-style methods are removed. Devices live on the client as reactive lists that auto-update when devices are plugged in or removed.
Heads up: v4 device selectors take the full
MediaDeviceInfoobject, not just adeviceIdstring.
Before (v3):
After (v4):
Subscriber is renamed to User.
Before (v3):
After (v4):
v3’s paginated client.address.getAddresses() is replaced by a reactive directory that accumulates entries on loadMore().
Before (v3):
After (v4):
You can dial an address directly:
v3’s client.conversation API is replaced by per-address messaging on the call.
Before (v3):
After (v4):
Messages are scoped to the current call’s address — there is no global conversation client in v4.
The standalone Chat, PubSub, and WebRTC clients from v3 are removed. Device APIs move onto the client (see Device management). Chat/PubSub equivalents are not part of the v4 browser SDK.
When using RxJS operators like
filter,map, orpipe, import them fromrxjs:See the RxJS primer for a quick orientation.
v4 requires explicit cleanup. End calls with hangup(), then disconnect and destroy the client to release all subscriptions.
v4 ships @signalwire/web-components, composable around the new reactive Call API. <sw-call-media> is the root container — nest media, controls, and status components inside, then assign the call.
<sw-participants> renders participant overlays driven by the same context.
remoteStream$ (and localStream$) and assign the stream to a <video>’s srcObject — or use <sw-call-media> / <sw-self-media>.call.self is null. self is populated only after joining. Use call.self$ for reactive access, or optional chaining (call.self?.audioMuted) for sync reads.participants$ re-emits the full list on any change, so wire it up early in your component lifecycle.startRecording() throws. Recording is not yet implemented in v4. Trigger recording server-side via SWML or the REST API; use call.recording$ to reflect state in the UI.MediaDeviceInfo object, not a bare deviceId string.client.updateToken() is gone. Implement refresh() on your credential provider and return { token, expiry_at } — the SDK will refresh on schedule.