Skip to main content

Voice SDK (beta)

warning

Voice SDK is currently in beta. Please contact our support team if you encounter any issues or have feedback.

Drop-in WebRTC dialer widget and headless SIP client for embedding on your website. Your customers place calls straight from the page; the SIP infrastructure and billing are handled by didlogic, under your account.

Architecture

   ┌──────────┐   POST {command:"call"}   ┌────────────────┐   POST + api_token   ┌──────────────────┐
│ Browser │ ────────────────────────► │ Your backend │ ───────────────────► │ didlogic API │
│ <dl-...> │ │ (proxy) │ │ /mobile/api/sdk │
└──────────┘ ◄──────────────────────── └────────────────┘ ◄─────────────────── └──────────────────┘
{session_id, session_pwd, {session_id, session_pwd,
wss_url, expires_in} wss_url, expires_in}

The key principle: the api_token (a.k.a. SdkApiToken) lives on your backend only. The widget talks exclusively to your proxy; the proxy adds the token and forwards the request to didlogic. The widget gets back ephemeral SIP credentials (TTL ~1 hour), registers over WSS, and places the call.

Install

npm install @didlogic/voice-sdk

Quickstart — ESM

import '@didlogic/voice-sdk'

document.body.innerHTML = `
<dl-dialer
proxy-url="https://your-backend.example.com/webrtc"
brand-name="Acme Phone"
></dl-dialer>
`

The side-effect import registers every custom element. After that, <dl-dialer> renders from any framework as a regular web component. The proxy-url attribute is required — it points to your backend endpoint that issues short-lived SIP credentials (see Setup on your backend).

Quickstart — React

import '@didlogic/voice-sdk'

export function PhoneWidget() {
return (
<dl-dialer
proxy-url="https://your-backend.example.com/webrtc"
brand-name="Acme Phone"
/>
)
}

The package automatically augments JSX.IntrinsicElements for React 18 (global JSX) and React 19 (the react module's JSX), so <dl-dialer> is recognised in TSX with no extra declaration.

info

For other JSX runtimes (Vue / Svelte / Solid / Preact), add your own augmentation against the widget's exported classes.

Setup on the didlogic portal

To let the widget place calls, you need to create an SdkApiToken for your didlogic account — a long-lived bearer token your backend will inject into requests.

What SdkApiToken is

  • 1 token = 1 didlogic user (the user calls are placed and billed against).
  • Bound to the SIP friend / DID the widget registers under.
  • Can be enabled / disabled / revoked at any time — deactivation tears down active sessions immediately.

To get one, contact didlogic support with your account login — we'll create the token and deliver it through a secure channel. A self-service UI is on the roadmap but not available in v1.0.

Once you have the token:

  1. Put it in your backend secrets (env, vault — anything, as long as it's not in the browser and not in git).
  2. Ensure the user has a SIP friend and an active DID with outbound calling enabled.
  3. didlogic-side rate limits:
    • 60 requests per minute per token
    • 120 requests per minute per IP Exceeding either → 429 Too Many Requests.
  4. If the user is blocked (negative balance, suspended), didlogic returns 403 Forbidden.

How API errors surface in the widget

HTTP from didlogicWhat dl-dialer shows
401 Unauthorized"Authentication failed" (token invalid / revoked)
403 Forbidden"Access denied" (user blocked, limits)
429 Too Many Requests"Too many requests, please retry later"
Any 5xxAuto-retry with exponential backoff (cap 5 min, up to 10 tries)

Setup on your backend (proxy)

The proxy is a single HTTP endpoint on your side that:

  1. Accepts a POST from the widget with JSON { "command": "call" }.
  2. Adds an api_token field (value from env) to the body.
  3. Forwards the request to https://<didlogic-host>/mobile/api/sdk/command.
  4. Returns the response as is.

Contract

From the widget to your proxy:

POST /webrtc        HTTP/1.1
Content-Type: application/json

{ "command": "call" }

From your proxy to didlogic:

POST /mobile/api/sdk/command        HTTP/1.1
Content-Type: application/json

{ "command": "call", "api_token": "<SDK_API_TOKEN>" }

didlogic response (forwarded to the widget verbatim):

{
"session_id": "1001@your-tenant",
"session_pwd": "<short-lived-secret>",
"wss_url": "wss://sip.didlogic.com:7443",
"expires_in": 3600
}

Reference proxy in Node.js (zero dependencies)

// proxy.mjs — node 18+
import { createServer } from 'node:http'

const PORT = process.env.PORT ?? 3000
const SDK_API_TOKEN = process.env.SDK_API_TOKEN
const UPSTREAM = process.env.UPSTREAM_URL ?? 'https://didlogic.com/mobile/api/sdk/command'
const ALLOW_ORIGIN = process.env.ALLOW_ORIGIN ?? '*'

if (!SDK_API_TOKEN) {
console.error('SDK_API_TOKEN env is required')
process.exit(1)
}

const cors = {
'Access-Control-Allow-Origin': ALLOW_ORIGIN,
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
}

createServer(async (req, res) => {
if (req.method === 'OPTIONS') return res.writeHead(204, cors).end()
if (req.method !== 'POST' || req.url !== '/webrtc') {
return res.writeHead(404, cors).end()
}

const chunks = []
for await (const chunk of req) chunks.push(chunk)
const body = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}')

const upstream = await fetch(UPSTREAM, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...body, api_token: SDK_API_TOKEN }),
})

const text = await upstream.text()
res.writeHead(upstream.status, {
'Content-Type': upstream.headers.get('content-type') ?? 'application/json',
...cors,
})
res.end(text)
}).listen(PORT, () => console.log(`proxy on :${PORT}/webrtc`))

Run it:

SDK_API_TOKEN=<token> ALLOW_ORIGIN=https://your-app.example.com node proxy.mjs
warning

Never put SDK_API_TOKEN in the browser. Not in an HTML attribute, not in a JS bundle, not in a frontend env file. Any leak of the token into client code = compromise. Keep it in server-side env only.

What the proxy must do (minimum)

  • Accept only POST on its public path.
  • Handle the CORS preflight (OPTIONS) for the domain hosting the widget.
  • Inject api_token from a server-side secret.
  • Forward didlogic's body and status code as is — otherwise the widget can't handle 401/403/429 correctly.

What the proxy may do (optional)

  • Authenticate the call via your existing session cookie / JWT — to issue SIP credentials only to logged-in users.
  • Log, rate-limit per your own users, or rewrite command based on business rules.
  • Audit who's hitting the widget and how often.

dl-dialer attributes

AttributeTypeDefaultDescription
proxy-urlstring(required)URL of your proxy endpoint (see above)
brand-namestring"Phone"Title shown in the widget header
click-to-callbooleanfalseScan the host page and intercept clicks on tel: links
click-to-call-selectorstring""CSS selector limiting the click-to-call scan area

For per-element autocomplete in IDEs that consume the Custom Elements Manifest (VS Code + lit-plugin, WebStorm, Storybook), use the [custom-elements.json](./custom-elements.json) shipped with the package.

Events

<dl-dialer> is self-contained: it owns its UI state and talks to the SIP stack internally. No custom events are emitted on the element itself.

If you need call lifecycle events (registered, accepted, ended, failed, …), use the Headless API. Device and VoiceCall expose typed event emitters.

Theming

CSS custom properties applied to <dl-dialer> or any of its ancestors:

VariableDefaultPurpose
--dl-primary#447bf2Accent (digits on the display)
--dl-success#66d391Call button background
--dl-danger#ed2643Hangup button background
--dl-danger-hover#d41e38Hangup hover state
--dl-bg#ffffffWidget background
--dl-surface#f0f0f0Inner surface tint
--dl-surface-hover#e4e4e4Inner surface hover
--dl-text#121011Primary text
--dl-text-secondary#9298a3Secondary text
--dl-border#e7eaf0Borders, separators
--dl-font-familyKlavikaBasicRegular, Arial, sans-serifDefault UI font
--dl-font-monoPTMonoRegular, Arial, serifMonospace (digits)
--dl-radius6pxDefault corner radius
--dl-radius-round50%Round button radius
--dl-width360pxWidget width
--dl-height680pxWidget height
--dl-sheet-height100%Bottom-sheet (DTMF) height
dl-dialer {
--dl-primary: #ff6b00;
--dl-success: #00aa44;
--dl-width: 320px;
--dl-height: 600px;
}

The widget ships with its own fallback fonts. To restyle, override --dl-font-family / --dl-font-mono with your own stack.

Headless API

For non-widget integrations the SDK exposes the underlying classes:

import { Device, CallCredentialsManager } from '@didlogic/voice-sdk'

const credentials = new CallCredentialsManager({
proxyUrl: 'https://your-backend.example.com/webrtc',
})
const device = new Device()

credentials.onCredentials = (creds) => device.applyCredentials(creds)
credentials.onError = (err) => console.error('credentials error', err)
await credentials.fetch()

device.on('registered', () => console.log('SIP registered'))
device.on('registrationFailed', (e) => console.error('register failed:', e.cause))

const call = await device.call('+15551234567')
call.on('accepted', () => console.log('connected'))
call.on('ended', () => console.log('hung up'))
call.on('failed', () => console.log('call failed'))

call.mute()
call.sendDTMF('5')
call.hangup()

CallCredentialsManager on its own:

  • caches the current credentials and refreshes them automatically refreshBeforeExpirySec (default 60) before expires_in;
  • retries refresh failures with exponential backoff (cap 5 min, up to 10 attempts);
  • supports pauseRefresh() / resumeRefresh() (the widget pauses while a call is active so a re-register does not drop the line).

Full export list

Runtime classes:

  • Device, VoiceCall, AudioManager — the low-level SIP/WebRTC stack (re-exported from @didlogic/voice-core)
  • CallCredentialsManager — proxy-backed authentication
  • Dialer — the widget class itself, in case you want to subclass it
  • Screen classes: Dialpad, Keypad, CallScreen, CallControls, CallerInfo, SettingsPanel
  • Reusable components: AppBar, Select, IconButton, BottomSheet
  • ClickToCallScanner — programmatic page scanning

Type-only:

DeviceOptions, DeviceCredentials, AudioDeviceInfo, DeviceEventMap, CallEventMap, CallCredentials, CallCredentialsError, CallCredentialsManagerOptions, CallStatus, SelectOption, IconButtonVariant, ClickToCallOptions.

Click-to-Call

The click-to-call attribute turns on host-page scanning and intercepts clicks on tel: links and elements carrying data-phone / data-tel / data-number:

<dl-dialer
proxy-url="https://your-backend.example.com/webrtc"
click-to-call
click-to-call-selector=".article-body"
></dl-dialer>

<a href="tel:+15551234567">Call us</a>
<span data-phone="+15551234567">+1 555 123-4567</span>

The scanner:

  • catches tel: hrefs and elements with data-phone | data-tel | data-number;
  • finds bare phone numbers in text nodes via a regex;
  • uses MutationObserver — works in SPAs without page reloads;
  • injects a <style id="dl-click-to-call-styles"> with a CSS class .dl-click-to-call (with a hover state). Colors are configurable via --dl-click-to-call-color / --dl-click-to-call-hover-color.

For programmatic use, see ClickToCallScanner in the Headless API.

Browser support

Modern evergreen browsers with WebRTC and Web Components:

  • Chrome / Edge ≥ 100
  • Firefox ≥ 100
  • Safari ≥ 15

WebRTC requires HTTPS (or localhost). Microphone access is requested on the first call.

Versioning

Semantic Versioning. Breaking changes ship in major releases only. The documented attributes, events and CSS variables are the public API; anything not described here is internal and may change at any time.