> Fetch clean Markdown by appending `.md` to any page URL under https://signalwire.com/docs or requesting it with the HTTP header `Accept: text/markdown`. The root index at https://signalwire.com/docs/llms.txt lists the available documentation indexes. # Overview 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](/docs/swml), and all telephony and communication services SignalWire provides. How would you like to get started? #### [Make a video call with JS](#your-first-call) 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](/docs/browser-sdk/v4/guides/web-components) Add `` or `` 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 ` ``` ## Your first call ```html ``` ```js import { SignalWire, StaticCredentialProvider } from "@signalwire/js"; const client = new SignalWire( new StaticCredentialProvider({ token: "YOUR_SUBSCRIBER_ACCESS_TOKEN" }) ); let activeCall; client.ready$.subscribe(async (ready) => { if (!ready) return; activeCall = await client.dial("/public/test-room", { audio: true, video: true, receiveAudio: true, receiveVideo: true, }); activeCall.localStream$.subscribe((s) => { document.getElementById("localVideo").srcObject = s; }); activeCall.remoteStream$.subscribe((s) => { document.getElementById("remoteVideo").srcObject = s; }); activeCall.status$.subscribe((status) => console.log("status:", status)); }); document.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](/docs/browser-sdk/v4/guides/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**](/docs/browser-sdk/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](https://my.signalwire.com/?page=api) Pass them to the one-call helper: ```js import { embeddableCall } from "@signalwire/js"; const call = await embeddableCall({ host: "yourspace.signalwire.com", embedToken: "YOUR_C2C_TOKEN", to: "/public/support", }); ``` `embeddableCall` builds the client, connects, and dials in a single call. Embed tokens are safe to expose in client code — see [Authentication](/docs/browser-sdk/v4/guides/authentication) for the full token model. ## Receiving inbound calls To accept incoming calls, register the client and watch the inbound list: ```js await client.register(); client.session.incomingCalls$.subscribe((calls) => { const ringing = calls.find((c) => c.status === "ringing"); if (!ringing) return; document.getElementById("accept").onclick = () => { ringing.answer({ audio: true, video: true }); // Then bind `ringing.localStream$` / `ringing.remoteStream$` to your //