Browser Softphone — place and receive calls from the dashboard
The Browser Softphone is the dashboard’s built-in phone. It places and receives real calls from a browser tab, over WebRTC, without a desk phone, a SIP app, or a browser extension. It is the same underlying path the floating dialer and the SDK-demo test page ride: a short-lived LiveKit token joins a per-user voice room, then a dial request bridges the room to the PSTN through jambonz and the Devotel softswitch — the org’s wholesale outbound path. This page is not an external SIP client; it’s the in-dashboard surface.1. What it is
The Browser Softphone page lives under Voice → Softphone.POST /voice/softphone/token issues a short-lived credential for the
current user’s deterministic room (softphone:<tenant>:<user>), and
POST /voice/softphone/dial starts the PSTN leg. Outbound termination
exits through jambonz → the Devotel softswitch, exactly like every other
production voice path. There is no SIP REGISTER step on this surface
— the browser joins over the signed-in dashboard session and listens for
inbound frames. That is also why the surface deliberately does not
use the Web SDK Softphone class (the raw SIP-over-WSS register path)
described on the Web SDK page: that class depends on a
SIP WSS gateway that is not provisioned in most environments, so its
register call is rejected. The dashboard uses the LiveKit path instead,
which works out of the box.
The floating softphone shell on every dashboard route, the global
IncomingCallDialog, and the Developer → SDK Live Demo page all ride the
same useSoftphone() state machine, so this page stays in lockstep with
the softphone experience everywhere else.
2. Role gate
Placing a call spends wallet credit and registers a callable identity, so only owner, admin, and developer roles reach the surface. Viewers, billing, and agent roles are excluded — same gate as the Developer SDK-demo page. If a teammate can’t see the page, an org owner must change their role; it is the same gate the Developer SDK-demo page carries.3. Inbound and outbound flows
At rest the page shows a Ready badge when the real-time event stream is connected (the LiveKit equivalent of “registered presence”), or a “Connecting to Orbit…” hint when it is not, since inbound rings arrive only when that stream is live. Outbound. Pick a caller ID from the organization’s active numbers (the picker lists every owned, active number), type the destination in E.164 form (for example+15559876543), and press Call. The dial
runs through the same POST /voice/softphone/token →
POST /voice/softphone/dial sequence the floating dialer uses, and the
page watches the badge climb Connecting… → Dialing… → Ringing… →
On call.
Inbound. A ringing Incoming call banner appears with Answer /
Decline buttons, tracking the shared IncomingCallDialog so two
dashboard tabs never double-answer. Accepting a call flips the badge to
On call.
Hang up. Hang up terminates the call; the badge falls back to
Call ended. Every call placed or accepted through this page is a
standard one — it lands in the Voice call logs with its caller/callee,
duration, recording, and the disposition tag the responsible party set,
so agent QA and analytics see the same trail the floating dialer
leaves.
4. Caller ID and number sources
The picker fetches your organization’s active numbers and defaults to your saved preferred caller ID (the same persisted setting the floating dialer and incoming-call reply share). An extension that provisions a direct number can deep-link here with?from=<E.164> to adopt that
number as the shared caller ID when it’s owned and active. If no owned
number can dial (all suspended, releasing, or still activating), the
page says so with the specific reason, instead of a generic “No active
numbers” state — pick a caller ID the ownership gate accepts, since the
dial itself rejects a non-owned number.
5. Limits
- Scope. Token and dial endpoints accept
voice:read/voice:writescope; the page gates to owner/admin/developer. - Per-second dial caps. The voice surface is rate-limited like
other read/write endpoints (read budget / write budget under
RATE_LIMIT_AUTH_READ/RATE_LIMIT_AUTH_WRITE); an idle softphone never dials and so never exhausts the write budget. - Concurrent sessions. The softphone room is scoped to the current
user, and two tabs opened on the same session share the same
useSoftphone()state, not two parallel call sessions.
6. When to use this versus your own SIP client
Use the Browser Softphone when the work is yours — a sales call, an escalation, a supervisor barge, a demo — and when the call start or accept should leave the same dashboard that hosts the rest of the workflow. Use your own SIP client (a desk phone, a Zoiper-like app, the Web SDKSoftphone embedded in your own app) when the agent works from
a fixed PBX seat, the organization also needs registrations on desk
phones, or you need classic SIP register semantics — the
registering-your-pbx-on-orbit
guide covers the PBX trunk side, and the Web SDK Softphone
describes embedding the softphone class in your own application. The
dashboard softphone does not use the SDK Softphone class — it rides
the LiveKit room directly, and it never SIP-REGISTERs.
7. Escape hatch — token + dial from the API
POST /voice/softphone/token (any owner/admin/developer credential; an
empty body is accepted):
POST /voice/softphone/dial with the destination and (optional)
caller ID:
useSoftphone()
context calls, so the live call flows under the same brand and billing
account as the page. If something hangs in the page you can re-create
the exact call sequence against the API and rule the surface in (or out).
See also
- Voice extensions (SIP credentials) — provision a
direct-dial extension so the
?from=deep-link adopts the right caller ID. - Registering your PBX on Orbit — the SIP trunk side, when a fixed seat and SIP register matter.
- Set up the browser (WebRTC) softphone — full inbound-routing, credential, and client wiring for the same surface on an embedded build.
- Web SDK Softphone — the embeddable SIP-over-WSS class when you need SIP register semantics in your own application.
- SDK demo test-call page — place a real outbound test call from Developer → SDK Live Demo.