Skip to main content

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

Or via CDN:
The Softphone path additionally requires jssip as an optional peer dep:
Chat-only consumers can skip it; the bundle is tree-shaken so you don’t pay the cost unless you import 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).
The SDK auto re-registers ~5 minutes before the SIP credential expires. Tear down with 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.
The service-worker bootstrap is published at a separate subpath. Import it from your service-worker file with an ES-module import — because of that, the worker must be registered as a module worker (navigator.serviceWorker.register('/orbit-push-sw.js', { type: 'module' })):
The SW must run with 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. Use OrbitCdpAnalytics only 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-key OrbitPersonalization surface above, which never needs the ingest secret.
For high-volume browser-adjacent surfaces, 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 live CallSession 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 the CallSession. 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 calls voice.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 sets record: 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 a BroadcastChannel):

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

For server-side / private-key operations (sending messages on behalf of the user, accessing PII, etc.) use the Node.js SDK — never the Web SDK with a secret key.

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.