Overview

View as MarkdownOpen in Claude

The SignalWire Browser SDK puts voice, video, and chat in a browser without plugins, downloads, or a media server you have to run. It also integrates with powerful AI agents, SWML, and all telephony and communication services SignalWire provides.

How would you like to get started?

Prerequisites

If you have Node + npm (or you can drop a <script> tag in an HTML file), you’re set. You’ll also need a Subscriber Access Token (SAT) — mint one from the SignalWire Dashboard’s Subscribers section.

For a quick experiment, or if you’re building a public widget like a chatbot or click-to-call button, you can use an embed token instead. Embed tokens are built primarily for embedded widget applications but work for many other use cases. See Trying it without a backend below.

Install

$npm install @signalwire/js@latest rxjs

RxJS is a peer dependency — the SDK uses observables for all reactive state. See the RxJS Primer when you’re ready to dig in.

For a quick script-tag setup, pin a version rather than @latest:

For a no-build setup, load the SDK as an ES module from a CDN that rewrites Node built-ins for the browser (esm.sh, jspm.io, skypack):

1<script type="module">
2 import { SignalWire, StaticCredentialProvider } from "https://esm.sh/@signalwire/js@dev";
3 // ... use SignalWire and StaticCredentialProvider as below
4</script>

Your first call

1<video id="localVideo" autoplay playsinline muted></video>
2<video id="remoteVideo" autoplay playsinline></video>
3<button id="hangup">Hang Up</button>
1import { SignalWire, StaticCredentialProvider } from "@signalwire/js";
2
3const client = new SignalWire(
4 new StaticCredentialProvider({ token: "YOUR_SUBSCRIBER_ACCESS_TOKEN" })
5);
6
7let activeCall;
8
9client.ready$.subscribe(async (ready) => {
10 if (!ready) return;
11
12 activeCall = await client.dial("/public/test-room", {
13 audio: true,
14 video: true,
15 receiveAudio: true,
16 receiveVideo: true,
17 });
18
19 activeCall.localStream$.subscribe((s) => {
20 document.getElementById("localVideo").srcObject = s;
21 });
22 activeCall.remoteStream$.subscribe((s) => {
23 document.getElementById("remoteVideo").srcObject = s;
24 });
25 activeCall.status$.subscribe((status) => console.log("status:", status));
26});
27
28document.getElementById("hangup").onclick = () => activeCall?.hangup();

If it worked, you’ll see your own camera in localVideo and a black frame in remoteVideo/public/test-room is empty until someone else joins. Open the same page in a second tab to see the remote stream light up. The browser console should log status: connected once media is flowing.

If your camera light isn’t on, check the Troubleshooting guide — usually permissions, HTTPS, or a denied microphone prompt.

Trying it without a backend

Embed tokens (c2c_… / c2t_…) are public tokens designed for embedded widgets — chatbots, click-to-call buttons, in-page call UIs — but they’re also the fastest way to prototype from a static HTML file without standing up a backend to mint SATs.

Create one by setting up a Click to Call resource in the dashboard (sidebar → ToolsClick to Call). From the resource you create, copy three values:

  • the resource address (e.g. /public/support) — what you’ll dial
  • the C2C token (c2c_…) — the embed token
  • your space name (e.g. yourspace.signalwire.com) — from the API Credentials section of the dashboard

Pass them to the one-call helper:

1import { embeddableCall } from "@signalwire/js";
2
3const call = await embeddableCall({
4 host: "yourspace.signalwire.com",
5 embedToken: "YOUR_C2C_TOKEN",
6 to: "/public/support",
7});

embeddableCall builds the client, connects, and dials in a single call. Embed tokens are safe to expose in client code — see Authentication for the full token model.

Receiving inbound calls

To accept incoming calls, register the client and watch the inbound list:

1await client.register();
2
3client.session.incomingCalls$.subscribe((calls) => {
4 const ringing = calls.find((c) => c.status === "ringing");
5 if (!ringing) return;
6
7 document.getElementById("accept").onclick = () => {
8 ringing.answer({ audio: true, video: true });
9 // Then bind `ringing.localStream$` / `ringing.remoteStream$` to your
10 // <video> elements and subscribe to `ringing.status$` for UI updates.
11 };
12 document.getElementById("decline").onclick = () => ringing.reject();
13});

Inbound calls require a Subscriber Access Token (SAT) issued for a specific user — embed tokens and guest tokens are outbound-only. The full accept-and-wire pattern, including caller name handling and ringing-UI teardown, lives in Inbound Calls.

Next steps

Reference