Web SDK
The Devotel Web SDK (@devotel/orbit-web-sdk) is the browser-side SDK that powers the embeddable Orbit Chat widget, the WebRTC softphone, on-site personalisation, and Web Push (VAPID) re-engagement notifications. It ships as native ES module + CommonJS builds with subpath exports per surface (./softphone, ./personalization, ./push, ./push/service-worker, ./widget) and has no peer-dependency on a framework (works in vanilla, React, Vue, Svelte, Solid).
Installation
jssip as an optional peer dep:
Softphone.
Quick Start
The SDK exports several product surfaces — pick only what you need. The Quick Start below wires the four most common (chat, softphone, personalisation, push); three further GA surfaces — the embeddable AI voice-agent widget (OrbitVoiceAgent), the CRM CTI bridge (OrbitCTI), and the Segment-compatible CDP analytics clients (OrbitCdpAnalytics / OrbitCdpAnalyticsQueue) — are documented under Modules below.
Modules
Chat widget — OrbitChat
Embeds the same Inbox-backed live chat that operators see on the dashboard. The widget supports text, file uploads, typing indicators, AI-agent fallback, and human-handoff escalation.
Softphone (SIP / WebRTC voice) — Softphone
A JsSIP-backed softphone that places outbound PSTN calls or joins inbound voice flows. Call control hooks into Orbit’s voice routing rules (IVR, AI agent, human handoff). Two-step auth: your server exchanges a server-issued JWT (or, less commonly, an API key) for short-lived SIP credentials via POST /voice/softphone/register, then the browser opens a JsSIP WSS to the Orbit WSS (Kamailio) gateway (the wssUrl returned by POST /voice/softphone/register).
softphone.unregister() to stop the renewal loop.
See Voice operations below for the full call-control surface (hangup, hold, mute, DTMF, transfer, conference, recording, webhooks).
Web Push subscriptions — OrbitPush
VAPID + RFC-8030/8291/8292 compliant push registration. Always feature-detect with OrbitPush.isSupported() first — Safari pre-16.4 and browsers without PushManager will throw otherwise.
import — because of that, the worker must be registered as a module worker (navigator.serviceWorker.register('/orbit-push-sw.js', { type: 'module' })):
scope: "/" to receive pushes for any path. The expectedTenantId guard is compared against each payload’s tenant_id, so it must be the tenant_<id> value your push payloads carry (not an org_-prefixed id) — any mismatch is dropped to prevent cross-tenant leakage.
Personalisation — OrbitPersonalization
Identifies a visitor and fetches their contact_scores.segment_label server-side. Use the segment to swap copy, gate features, or trigger CTAs.
AI voice-agent widget — OrbitVoiceAgent
An embeddable browser WebRTC widget that lets a visitor press “Talk to AI” and hold a live voice conversation with one of your configured AI voice agents — the web front-door to Orbit’s voice AI (web onboarding + live demos), no inbound PSTN DID required. It self-paints a button and a lightweight waveform into the container you pass, mints a short-lived room token via POST /widget/voice-agent-session, and runs the call over the same WebRTC media stack as the softphone. Like OrbitChat, it authenticates with your public key (today the tenant org id) — never a secret.
CRM CTI bridge — OrbitCTI
An iframe-embeddable click-to-dial + call-state bridge a CRM admin drops into a Salesforce / HubSpot / Zendesk sidebar — Orbit’s answer to the Genesys / Five9 / Talkdesk OpenCTI adapters. The host page fires events and calls dial(); all media transport lives inside the iframe, so the host page never touches jssip or livekit-client. Your backend mints the embed token server-side (or via a trusted same-origin proxy) and hands it to init().
CDP analytics (Segment-compatible) — OrbitCdpAnalytics
A Segment-compatible analytics ingest client — track, identify, alias, page, screen, group, batch — that signs each request (HMAC, timestamp + nonce) inside the SDK boundary using the WebCrypto API, so it runs in any modern browser, Cloudflare Workers, Vercel Edge, Deno, and Bun. It mirrors the Node SDK’s CdpAnalyticsClient on the same wire contract, so a request signed by either client verifies identically server-side.
This client holds an ingest secret, not a public key. The ingest secret MUST stay server-side — do not ship it in your page bundle. UseOrbitCdpAnalyticsonly on a trusted edge / SSR surface (read the secret from your environment), or proxy the signing through your own backend. For pure browser-side eventing, prefer the public-keyOrbitPersonalizationsurface above, which never needs the ingest secret.
OrbitCdpAnalyticsQueue wraps the same client with a sticky anonymousId, batched flush, retry with exponential backoff, and offline survival via localStorage — an analytics.js-style drop-in on top of the base OrbitCdpAnalytics.
Voice operations
The browser softphone is a thin client over the JsSIP WebRTC stack. Call control happens on the liveCallSession object for the active call leg; for surface-area calls like recording, conferences, transfers to PSTN, transcripts, and dialer campaigns, your backend uses the Node SDK against the same call_id the browser exposes via session.id.
All snippets below match the SDK source in packages/sdk-web/src/softphone/.
Make a call
Get call details
Live state is on theCallSession. For historical details, query the server SDK with session.id:
Hang up a call
Answer an incoming call
Hold and mute
Send DTMF
Transfer a call
The browser softphone supports blind transfer (SIP REFER) directly on the call session. Warm (consult-first) transfer is a server-orchestrated flow — your backend callsvoice.calls.warmTransfer(session.id, ...) while keeping the local leg alive.
Conference call
Conferencing is server-orchestrated — the browser stays in its own leg while the backend bridges additional participants:Record a call
Recording is enabled at call setup — your backend setsrecord: true when minting the softphone JWT, or when triggering the outbound call. After hangup, fetch the recording from your backend:
Listen to a recording
Once your backend has the signed URL, drop it into a standard HTML5 audio element — no SDK API required:Voice webhook handling
Voice webhooks are server-side — the browser SDK should never accept inbound HTTPS. Wire your backend (see the Node SDK voice webhook guide) and forward state to the browser via your existing channel (WebSocket, SSE, or aBroadcastChannel):
Configuration
Each surface (OrbitChat, Softphone, OrbitPersonalization, OrbitPush) takes its own constructor options. The API base URL is configured with a different option name on each surface — there is no single shared baseUrl.
Softphone is constructed via the Softphone.fromToken(jwt, opts) factory (where opts carries the outbound fromNumber and any per-call overrides), not a public key — see the Softphone module above.
Source
- npm: @devotel/orbit-web-sdk
- GitHub:
packages/sdk-web
Authentication
Web SDK surfaces (OrbitChat, OrbitPersonalization, OrbitPush) authenticate with public keys only (dv_live_pk_*). Public keys are scoped to surfaces that are safe to expose in browser code: chat widget submission, push subscription registration, personalisation events. They cannot send messages on behalf of arbitrary recipients, mutate billing, or access PII.
If you call the REST API directly from the browser, send the public key as Authorization: Bearer dv_live_pk_... whenever the X-API-Key custom header is blocked by CORS — the server accepts either form. See the authentication guide for details.
Softphone does NOT take a public key directly — your backend mints a short-lived JWT (≈ 60 minutes) via POST /voice/softphone/register using your dv_live_sk_* server key, and the browser receives only the JWT. The SDK then opens the SIP/WebRTC session against that JWT. This keeps your secret key server-side while still letting end-users place calls.
See the authentication guide for the full scope matrix.