For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Log inSign up
Support
GuidesReferenceClick-to-Call
GuidesReferenceClick-to-Call
  • Getting Started
    • Overview
    • Authentication
    • RxJS Primer
    • Migrate from v3
  • Web Components
    • Overview
    • Click-to-Call
    • Theming
    • Customization
  • Build Voice & Video Apps
    • Overview
    • Outbound Calls
    • Inbound Calls
    • Device Management
    • Screen Sharing
    • Call Controls
    • Layouts
    • Messaging & Chat
  • Manage Resources
    • Overview
    • Users
    • Address Book
    • Client Preferences
    • Capabilities
  • Deploy
    • Overview
    • Framework Integration
    • SSR & Next.js
    • Troubleshooting
LogoLogoSignalWire Docs
Log inSign up
Support
On this page
  • Prerequisites
  • Install
  • Your first call
  • Trying it without a backend
  • Receiving inbound calls
  • Next steps
  • Reference
Getting Started

Overview

|View as Markdown|Open in Claude|
Was this page helpful?
Edit this page

Authentication

Learn how to authenticate with the browser SDK client
Next
Built with

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?

Make a video call with JS

Drive everything yourself with @signalwire/js — client.dial(), observables, your own UI. Best when you want full control over how the call looks and behaves.

Drop in a web component

Add <sw-call-widget> or <sw-click-to-call> to a page and you’re done — a styled, working call UI with one element. Best for marketing sites, click-to-call buttons, and quick integrations.

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

@signalwire/js

GitHub

signalwire-js

$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 → Tools → Click 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

Build Voice & Video apps

Mute, layouts, screen share — flesh out the call UI.

Web Components

Drop in <sw-call-widget> or <sw-click-to-call> instead.

Authentication

Mint SATs from your backend and pick a refresh strategy.

RxJS Primer

Understand the observable patterns the SDK uses.

Reference

  • SignalWire — top-level client
  • StaticCredentialProvider, EmbedTokenCredentialProvider — credential providers
  • SignalWire.dial() — place an outbound call
  • SignalWire.register() / session.incomingCalls$ — receive calls
  • embeddableCall() — one-call helper for embed tokens