Skip to main content

Test your browser calling setup with the SDK demo

The dashboard ships a built-in test-call page, Developer → SDK Live Demo, at /developer/sdk-demo. It places a real outbound call from the browser through LiveKit/WebRTC — the same path the dashboard softphone and the Softphone component in the Web SDK use — so you can verify your whole browser-calling chain from one screen before you embed it in your own app. This page walks the demo end to end: what you need before you start, what the page does at each step, how to read success, how to read every common failure, and where to go next.

1. Prerequisites

Check these three things before you open the page — most demo failures trace back to one of them. Role. Placing a call spends wallet credit, so the page is restricted to the owner, admin, and developer roles. Agents, viewers, and billing roles can’t reach it. If the page doesn’t appear in your Developer menu, ask an org owner to grant you one of those roles. Wallet. The call bills per minute against your organization’s wallet. An empty wallet stops the dial at the guard with an INSUFFICIENT_BALANCE error, so top up first — a sandbox test call still needs a positive balance, even if the per-minute charge rounds to cents. LiveKit token basics. A LiveKit token is a short-lived, signed credential that authorizes your browser to join your organization’s voice room and publish audio into it. Every browser softphone on Orbit — the dashboard dialer, the Web SDK Softphone component, and this demo — gets one before it can send or receive a single audio frame. The demo fetches it for you; when you embed the softphone yourself you’ll fetch it the same way, through POST /voice/softphone/token. You also need a modern browser on HTTPS (the dashboard always is) and permission to grant it microphone access.

2. Open the demo page

In the dashboard, open the Developer section in the sidebar and choose SDK Live Demo. The route is /developer/sdk-demo. The page is a single card: an optional Caller ID field, a required Destination field, a Call button, and a status badge (Idle before you dial). Once a call is live you also get a state readout, a mute toggle, and a dial pad for DTMF.

3. The token step

When you press Call, the page runs the same two-step sequence every browser softphone uses:
  1. Get a LiveKit token — POST /voice/softphone/token returns the short-lived token. The browser uses it to join your organization’s LiveKit voice room and publish your microphone track into the room.
  2. Dial the PSTN leg — POST /voice/softphone/dial bridges the room to the destination number over the wholesale softswitch.
The token step is what makes the call a browser call: audio travels between your mic and the voice room over WebRTC, and only the far leg touches the phone network. If step 1 fails, the call never reaches step 2 — which is why the failure table below treats token and media-connection errors separately from dial-guard errors.

Token request and response

Fetch the token yourself the same way when you embed the softphone outside the dashboard:
The body is optional and ignored. The endpoint trusts whatever identity credential the call carries — a dashboard session cookie for in-dashboard fetch, an API key for embedded builds — so you don’t need a request body at all.
You get four fields back:
  • token — the LiveKit access token. It’s a JWT signed by Orbit’s media control plane with a one-hour TTL (DEFAULT_TOKEN_TTL_SECONDS = 3600 in orbit-media-manager). The browser hands it to LiveKit as the join credential.
  • room_name — the deterministic room the user will join (softphone:<tenant_id>:<user_id>). Reconnects land in the same room, so a refresh mid-call doesn’t strand state.
  • media_url — the WebSocket URL the browser connects to. This is the one field you need from the response for the client-side connect.
  • livekit_url — a deprecated alias of media_url. Use media_url; the alias will be removed in a future minor version.
Two fewer failure modes matter here than on most API calls:
  • 401 — you sent no recognizable credential (no session cookie, no API key), or the session/key has been revoked. Sign back in or re-key.
  • 403 — the credential is valid but INSUFFICIENT_PERMISSIONS (your role isn’t owner/admin/developer, or your API key lacks voice:write). Have an org owner raise the role or mint a voice-scoped key.

Joining with the token

The returned token is the second argument LiveKit’s Room.connect() expects. Point the client at media_url from the previous response (never at a hardcoded hostname):
Because the token is scoped to one room for one user, treat it like a credential a customer of your integration should never see in a network log: pass it between your own backend and browser over HTTPS and don’t persist it. The token lasts one hour, so a long-lived softphone should re-fetch before the expiry rather than waiting for a dropped join. The Web SDK softphone component handles this for you — its Softphone.fromToken(token, ...) call wraps the token fetch and refresh, and reuses this same POST /voice/softphone/token endpoint under the hood. Use the component rather than hand-rolling the LiveKit join whenever you can; this manual flow is what you fall back to when you need the raw LiveKit client.

4. Place the call

  1. Caller ID (optional): enter an E.164 number your org owns (for example +14155552671). Leave it blank and the API picks your first dialable number. Ownership is enforced either way — the caller ID gate is an ownership check, not a format check.
  2. Destination: enter the destination in E.164 format (for example +15559876543). Use a sandbox number or your own phone — not a customer.
  3. Press Call and allow the microphone prompt.
  4. Watch the state badge move through connecting → dialing → ringing → on_call.
Demo calls are real outbound calls, billed to your organization’s wallet by the minute. Sandbox destinations and your own phone are safe test targets; a customer’s number is not.
“Real outbound” is literal: this is not a loopback or a simulated leg. The destination rings on the public phone network, the PSTN leg is carried by the same wholesale softswitch that carries your production traffic, and the call shows up in your call records and billing the same way any outbound call does.

5. Interpret success

A healthy first call looks like this:
  • The badge climbs connecting → dialing → ringing → on_call within a few seconds.
  • You hear ringback, then the far side’s audio, in the browser tab.
  • The far side hears your microphone. Toggle the mute button; the button flips to a muted state and the far side goes silent.
  • The dial pad enables once the media leg connects (before that it is disabled with a hint). Press a digit and the far side hears the DTMF tone — the standard way to walk an IVR.
  • Hang up from the page. The badge returns to Call ended.
That sequence proves the whole chain end to end: microphone capture in the browser, the LiveKit token and room join, outbound audio publishing, the softswitch PSTN leg, and DTMF signalling. Whatever works here will work in your embedded softphone, because the embedded component runs the identical sequence.

6. Interpret failures

A failed call lands on a red Failed badge, a short toast headline, and a specific inline reason in the card. Match the signature to the cause: About that SIP-WSS signature. The Web SDK also exposes a raw SIP-over-WSS register path for teams that want classic SIP semantics. Registering a raw SIP client requires a provisioned SIP WSS gateway, and in most environments that gateway is not provisioned — the register is rejected and the client reports a voice-gateway connection failure. The dashboard softphone and this demo deliberately avoid that path and ride LiveKit/WebRTC instead, which works out of the box. If your own embedded client fails at register time with that signature, the fix is architectural, not a retry: move to the LiveKit-based Softphone component.

7. Next steps to embed

When the demo passes, move to the browser softphone setup guide. It covers device registration, softphone credentials, inbound routing, and embedding the softphone in your own app. The hand-off is deliberately small: the two-step sequence — fetch a LiveKit token, then dial the PSTN leg — is identical between the demo and your embedded client. The Web SDK Softphone component wraps those exact two calls; the demo page merely exposes them as a manual form. What the demo does not cover, and the setup guide adds, is credential management for your agents, inbound call routing to the softphone, and the optional SIP-over-WSS register path for raw SIP clients.

8. FAQ

Who pays for demo calls? Your organization’s wallet. Every demo call bills per minute exactly like a production outbound call, which is why the page warns against dialing customers and why the wallet check is a prerequisite. Why is the page role-gated? Because it spends money. Owner, admin, and developer roles can commit wallet credit; agents, viewers, and billing roles cannot, so the page excludes them. If you need a teammate to run the demo, an org owner grants them one of the three allowed roles. Should I embed via LiveKit or raw SIP-over-WSS? LiveKit. The LiveKit path is the one the dashboard and this demo prove end to end and the one that works without extra provisioning. The raw SIP-WSS path exists for teams that specifically need standard SIP register and call-control semantics, and it depends on a SIP WSS gateway being provisioned for your environment — until it is, register fails. Start with the Softphone component; revisit SIP only if you have a hard SIP-semantics requirement.