Voice SDK (beta)
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.
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:
- Put it in your backend secrets (env, vault — anything, as long as it's not in the browser and not in git).
- Ensure the user has a SIP friend and an active DID with outbound calling enabled.
- didlogic-side rate limits:
- 60 requests per minute per token
- 120 requests per minute per IP Exceeding either →
429 Too Many Requests.
- If the user is blocked (negative balance, suspended), didlogic returns
403 Forbidden.
How API errors surface in the widget
| HTTP from didlogic | What 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 5xx | Auto-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:
- Accepts a
POSTfrom the widget with JSON{ "command": "call" }. - Adds an
api_tokenfield (value from env) to the body. - Forwards the request to
https://<didlogic-host>/mobile/api/sdk/command. - 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
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
POSTon its public path. - Handle the CORS preflight (
OPTIONS) for the domain hosting the widget. - Inject
api_tokenfrom a server-side secret. - Forward didlogic's body and status code as is — otherwise the widget can't handle
401/403/429correctly.
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
commandbased on business rules. - Audit who's hitting the widget and how often.
dl-dialer attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
proxy-url | string | (required) | URL of your proxy endpoint (see above) |
brand-name | string | "Phone" | Title shown in the widget header |
click-to-call | boolean | false | Scan the host page and intercept clicks on tel: links |
click-to-call-selector | string | "" | 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:
| Variable | Default | Purpose |
|---|---|---|
--dl-primary | #447bf2 | Accent (digits on the display) |
--dl-success | #66d391 | Call button background |
--dl-danger | #ed2643 | Hangup button background |
--dl-danger-hover | #d41e38 | Hangup hover state |
--dl-bg | #ffffff | Widget background |
--dl-surface | #f0f0f0 | Inner surface tint |
--dl-surface-hover | #e4e4e4 | Inner surface hover |
--dl-text | #121011 | Primary text |
--dl-text-secondary | #9298a3 | Secondary text |
--dl-border | #e7eaf0 | Borders, separators |
--dl-font-family | KlavikaBasicRegular, Arial, sans-serif | Default UI font |
--dl-font-mono | PTMonoRegular, Arial, serif | Monospace (digits) |
--dl-radius | 6px | Default corner radius |
--dl-radius-round | 50% | Round button radius |
--dl-width | 360px | Widget width |
--dl-height | 680px | Widget height |
--dl-sheet-height | 100% | 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(default60) beforeexpires_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 authenticationDialer— 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 withdata-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.