Integrate the SecuredCalls Web SDK
The SecuredCalls Web SDK is a JavaScript/TypeScript SDK for web apps and web-based dialers. It handles two things: branding calls and running on-device biometric verification — it does not place the call itself.
Estimated time: 35 minutes
Overview
The Web SDK covers the branding and verification lifecycle that happens before, during, or after a call. It works server-side in Node.js or client-side in the browser, and is token-only — your business secret never enters the browser.
The SDK provides two capabilities:
- Call branding — set up, clear, and annotate branded calls.
- Biometric verification — over a real-time data channel, ask the consumer’s device to perform an on-device biometric check (Face ID / Touch ID) and receive the result.
This SDK does not manage the actual voice or video call. It is solely responsible for the branding and verification lifecycle that happens before, during, or after a call session.
You build your own UI. The SDK exposes data and events; rendering status panels, verify buttons, and result badges is up to your application. The examples below show the recommended patterns — adapt them to your framework.
Topics
Jump to any part of the integration.
Add the package via npm or include the bundled browser script.
Pass a server-issued token — no secret in the browser.
Mint a short-lived bearer token server-side from your secret.
Set up branding, wire callbacks, clear branding, and add notes.
Open a data channel and run an on-device Face ID / Touch ID check.
Map raw outcomes to friendly labels in your own widget.
Drop the SDK into an HTML page and brand a call.
setupBrandingAsync, clearBrandingAsync, and addNoteAsync.
callConnectedAsync, triggerBiometricAsync, and callDisconnectedAsync.
Prerequisites
Make sure your environment is ready.
- A SecuredCalls account and your configuration string from the Secured Calls Portal
- A way to mint short-lived access tokens server-side using your business secret
- Node.js and npm (for the Node.js integration), or any web page (for the browser integration)
- For the browser build, the bundled JavaScript file from the SecuredCalls team
Section 1
Install the SDK
Choose the install path that matches your environment. The browser build is distributed separately by the SecuredCalls team.
Install for Node.js
Install the SDK from npm into your Node.js project.
1npm install @expertstack-studios/sc-web-sdk-v2Include the browser build
For a web application, include the compiled SDK file directly in your HTML.
Currently you will have to get in touch with the Secured Calls team to download the bundled JavaScript file.
1<script src="secured-calls-web-sdk-vx.x.x.js"></script>Section 2
Initialize the client
The SDK is token-only. You obtain a short-lived access token server-side and pass it to the SDK. The SDK sends that bearer token alone — no secret, no client-side signing or encryption — so your business secret never enters the browser.
Create the client
Construct SecuredCalls with your configuration, a server-issued token, and an optional loggingConfig.
configuration is available from the Secured Calls Portal. The token
must be minted on your server using your secret — see Acquire an access
token below — and handed to the browser. Never embed your secret in
client-side code.
1import { SecuredCalls } from '@expertstack-studios/sc-web-sdk-v2';2 3const scClient = new SecuredCalls({4 configuration: '<your-configuration>',5 token: '<access-token-issued-server-side>',6 loggingConfig: { level: 'info' },7});Options
Constructor options passed to new SecuredCalls({ … }).
Configuration string for your contact center / app, from the Secured Calls Portal. Required.
A bearer access token, issued server-side. Required.
Optional. Log verbosity — useful during integration.
Section 3
Acquire an access token
The SDK does not fetch its own token — your server mints one and passes it to the browser. This keeps your secret server-side. The configuration string is not sensitive — it only carries your clientId and the API/WebSocket hostnames; only the secret must stay server-side.
Call the auth-token endpoint
Your server authenticates to the Secured Calls auth-token endpoint with HTTP Basic auth.
secret— your business secret from the Secured Calls Portal.{clientId}and{domain}are encoded in yourconfigurationstring. Base64-decodeconfigurationto get a colon-separatedclientId:domain:wsDomain; use the first field asclientIdand the second asdomain(defaults toau.api.securedcalls.comif absent).
1POST https://{domain}/branding/cc/auth-token2Authorization: Basic base64("{clientId}:{secret}")Read the response
The response contains the bearer token and its expiry.
1{2 "scResponse": {3 "code": "...",4 "message": "...",5 "data": {6 "token": "<bearer-token>",7 "expiresAt": "<ISO-8601 timestamp>"8 }9 }10}Expose a token endpoint (Node.js / Express)
Expose an authenticated endpoint your frontend can call. Keep the secret in server config / environment variables, never in client code.
Protect this route with your own auth — only logged-in agents should reach it. Return only the token (and expiry) to the browser, never the secret.
1// server-side only2const CONFIGURATION = process.env.SC_CONFIGURATION!; // base64 "clientId:domain:wsDomain"3const SECRET = process.env.SC_SECRET!; // never sent to the browser4 5const [clientId, domain = 'au.api.securedcalls.com'] =6 Buffer.from(CONFIGURATION, 'base64').toString('utf8').split(':');7 8app.post('/sc/token', async (req, res) => {9 // Protect this route with your own auth — only logged-in agents should reach it.10 const credentials = Buffer.from(`${clientId}:${SECRET}`).toString('base64');11 12 const scRes = await fetch(`https://${domain}/branding/cc/auth-token`, {13 method: 'POST',14 headers: {15 'Content-Type': 'application/json',16 Authorization: `Basic ${credentials}`,17 },18 });19 20 if (!scRes.ok) {21 return res.status(502).json({ error: `auth-token failed: ${scRes.status}` });22 }23 24 const body = await scRes.json();25 const token = body?.scResponse?.data?.token;26 const expiresAt = body?.scResponse?.data?.expiresAt;27 if (!token) {28 return res.status(502).json({ error: 'no token in response' });29 }30 31 // Return only the token (and expiry) to the browser — never the secret.32 res.json({ token, expiresAt });33});Fetch the token, then build the SDK
In the browser, fetch a token from your own endpoint, then construct SecuredCalls with it.
Token lifetime: tokens are short-lived (expiresAt). Acquire a fresh
token per session/call rather than caching one long-term, and construct a new
SecuredCalls instance with the new token when it changes.
1import { SecuredCalls } from '@expertstack-studios/sc-web-sdk-v2';2 3// Your own endpoint from the server example above.4const { token } = await fetch('/sc/token', { method: 'POST' }).then((r) => r.json());5 6const scClient = new SecuredCalls({7 configuration: '<your-configuration>', // safe in the browser (no secret inside)8 token,9});Section 4
Call Branding
Subscribe to the branding events, then set up branding for a call. On success you receive a branding reference ID — keep it to clear branding or add notes later.
Brand a call
Register the three branding callbacks, then call setupBrandingAsync(...):
- onBrandingSuccess — fires with a
referenceId; start your call here and keep thereferenceIdto clear branding later. - onBrandingFailed — handle branding failures.
- onBrandingTimedOut — handle branding timeouts.
Use clearBrandingAsync(referenceId) when the call ends, and addNoteAsync(referenceId, note) to attach a read-only note shown to the recipient.
Pass "PSTNCall" or "InAppCall" as the call type (default "InAppCall"),
and a timeout in milliseconds before the attempt is considered failed
(default 20000).
1import { SecuredCalls } from '@expertstack-studios/sc-web-sdk-v2';2 3const scClient = new SecuredCalls({4 configuration: '<your-configuration>',5 token: '<access-token>',6 loggingConfig: { level: 'info' },7});8 9scClient.onBrandingSuccess((referenceId) => {10 console.log(`Branding succeeded: ${referenceId}`);11});12scClient.onBrandingFailed((error) => {13 console.error(`Branding failed: ${error}`);14});15scClient.onBrandingTimedOut(() => {16 console.warn('Branding timed out.');17});18 19await scClient.setupBrandingAsync(fromNumber, toNumber, 'support', 'PSTNCall', 20000);Section 5
Biometric Verification
Biometric verification runs over a real-time data channel. Open the channel when the call connects, register result listeners, trigger a check from your UI, and close the channel when the call ends.
Open the data channel
When the call connects, call callConnectedAsync(...). The response tells you whether the channel is enabled and whether biometrics are available on this number.
All three fields are required: businessNumber, consumerIdentifier, and uniqueContactCenterCallId.
channel.enabled is false when verification is unavailable for the number.
The channel can be open while channel.biometric.enabled is false — check
both before offering a Verify button.
1import { SecuredCalls } from '@expertstack-studios/sc-web-sdk-v2';2 3const scClient = new SecuredCalls({4 configuration: '<your-configuration>',5 token: '<access-token>',6});7 8// Open the data channel once the call is connected.9const channel = await scClient.callConnectedAsync({10 businessNumber: '61111222333', // your business number on the call11 consumerIdentifier: '61123456789', // the consumer's number12 uniqueContactCenterCallId: '<call-id>', // your unique id for this call13});14 15if (!channel.enabled) {16 // Verification is not available on this number.17} else if (!channel.biometric.enabled) {18 // The channel is open but biometrics are not enabled for this number.19} else {20 // Biometrics are available — register listeners and trigger a check.21}Listen for outcomes
Register onBiometricResult(...) and onBiometricError(...) before triggering a check — the outcome arrives asynchronously on these listeners, not as the return value of the trigger call.
1// Listen for outcomes before triggering a check.2scClient.onBiometricResult((result) => {3 if (result.verified) {4 console.log('Verified', result.reason); // e.g. "FaceIdMatch"5 } else {6 console.log('Not verified', result.reason);7 }8});9 10scClient.onBiometricError((err) => {11 console.warn(`Biometric error: ${err.code} — ${err.message}`);12});Trigger a check
Call triggerBiometricAsync() — typically from a Verify button. The consumer’s device prompts for Face ID / Touch ID, and the outcome arrives on your listeners.
The data channel must be open before you trigger a check, or the request fails
with CallNotConnected.
1// Trigger an on-device check (e.g. from a "Verify" button).2await scClient.triggerBiometricAsync();Close the channel
Call callDisconnectedAsync() when the call ends. It is safe to call even if the channel was never connected.
1// Close the channel when the call ends.2await scClient.callDisconnectedAsync();Section 6
Build a verification UI
The SDK gives you raw outcomes; you decide how to present them. The reason on a result is a device-supplied string such as FaceIdMatch or TouchIdMatch.
Map reasons to friendly labels
Map raw reasons to labels your agents can read. Keep a default branch that falls back to the raw value so a new device reason still renders rather than disappearing.
Reasons and error codes are an open set — always keep a default/fallback branch so unmapped values are shown rather than dropped.
1/** Map a raw biometric reason to a user-friendly label. Fall back to the raw2 * value so a new device reason still renders rather than disappearing. */3function describeBiometricReason(reason: string): string {4 switch (reason) {5 case 'FaceIdMatch': return 'Face ID';6 case 'TouchIdMatch': return 'Touch ID';7 case 'FaceIdMismatch': return 'Face ID — no match';8 case 'TouchIdMismatch': return 'Touch ID — no match';9 case 'BiometricMatch':10 case 'BiometricVerified': return 'Biometric match';11 case 'BiometricMismatch': return 'Biometric — no match';12 case 'CancelledByUser': return 'Cancelled by consumer';13 case 'CancelledByAgent': return 'Cancelled by agent';14 case 'BiometricNotEnrolled': return 'No biometrics enrolled';15 case 'BiometricPermissionDenied': return 'Permission denied';16 case 'BiometricUnavailable': return 'Biometrics unavailable';17 case 'BiometricLockedOut': return 'Biometrics locked';18 default: return reason; // unknown — show as-is19 }20}Wire it to a minimal flow
Count attempts, render each outcome with its friendly label, surface errors, and trigger a check from your Verify button.
1let attempts = 0;2 3scClient.onBiometricResult((result) => {4 attempts += 1;5 const label = result.reason ? describeBiometricReason(result.reason) : '';6 renderAttempt({7 n: attempts,8 verified: result.verified,9 detail: label, // e.g. "Verified · Face ID"10 });11});12 13scClient.onBiometricError((err) => {14 showError(err.message); // e.g. "Maximum biometric attempts reached for this call."15});16 17// Called from your "Verify" button:18async function onVerifyClicked() {19 await scClient.triggerBiometricAsync();20}Section 7
Browser quick start
The browser build exposes a global SecuredCallsSDK. Include the bundled script, create the client with your configuration and token, wire the success callback, and start branding.
Brand a call from the browser
Create the client with new SecuredCallsSDK.SecuredCalls({ configuration, token }), then call setupBrandingAsync(...).
Use a short-lived, server-issued token here — never expose a production secret in a public web page.
1<script src="secured-calls-web-sdk-vx.x.x.js"></script>2<script>3 const scClient = new SecuredCallsSDK.SecuredCalls({4 configuration: '<your-configuration>',5 token: '<access-token>',6 });7 8 scClient.onBrandingSuccess((referenceId) => {9 console.log(`Branding successful: ${referenceId}`);10 });11 12 scClient.setupBrandingAsync('+61123456789', '+61111222333', 'support', 'PSTNCall', 20000);13</script>Section 8
Branding API
The SecuredCalls client exposes three async methods for managing call branding and notes.
setupBrandingAsync
Initiates branding. Resolves to a branding reference ID.
setupBrandingAsync(fromNumber: string, toNumber: string, intent: string, callType?: string, timeout?: number): Promise<string>
The number initiating the call.
The number receiving the call.
The intent of the call (e.g. support).
Optional. 'PSTNCall' or 'InAppCall' (default 'InAppCall').
Optional. Milliseconds before the attempt is considered failed (default
20000).
clearBrandingAsync
Clears branding that was set up earlier.
clearBrandingAsync(referenceId: string): Promise<boolean>
The reference ID returned by setupBrandingAsync.
addNoteAsync
Attaches a read-only note shown to the recipient.
addNoteAsync(referenceId: string, note: string): Promise<boolean>
The reference ID of the call to attach the note to.
The note to attach to the call.
Branding events
Callbacks the client emits during the branding lifecycle.
onBrandingSuccess(referenceId)— branding succeeded;referenceIdidentifies the branded call.onBrandingFailed(errorMessage)— branding failed.onBrandingTimedOut()— branding timed out.
Section 9
Biometric API
Four methods manage the data channel and on-device verification.
callConnectedAsync
Opens the data channel. Returns channel and biometric availability.
callConnectedAsync(params: { businessNumber: string, consumerIdentifier: string, uniqueContactCenterCallId: string }): Promise<IDataChannelConnectResponse>
Your business number on the call. Required.
The consumer’s number. Required.
Your own unique identifier for this call. Required.
The resolved IDataChannelConnectResponse:
1interface IDataChannelConnectResponse {2 enabled: boolean; // false → channel was not opened3 dataChannelConnectionId?: string; // present when enabled and registered4 biometric: {5 enabled: boolean;6 maxAttemptsPerCall?: number; // verification attempts allowed per call7 };8}triggerBiometricAsync
Requests an on-device biometric check. Requires the channel to be open. The result is delivered asynchronously to onBiometricResult / onBiometricError.
triggerBiometricAsync(): Promise<void>
callDisconnectedAsync
Closes the data channel. Safe to call even if not connected.
callDisconnectedAsync(): Promise<void>
getDataChannelConfig
Returns the config from the most recent callConnectedAsync — available even if the socket failed — or null.
getDataChannelConfig(): IDataChannelConfig | null
Biometric events
Callbacks the client emits during verification.
onBiometricResult(result) — a check completed.
1interface IBiometricResult {2 requestId: string;3 verified: boolean;4 reason?: string; // device-supplied, e.g. "FaceIdMatch", "TouchIdMatch"5}onBiometricError(error) — the request was rejected or could not run.
1interface IBiometricError {2 code: string; // see codes below3 message: string;4}Biometric error codes
Treat error.code as a string enum — switch on the ones you handle and default for the rest, as new codes may be added.
| Code | Meaning |
|---|---|
CallNotConnected | The call isn’t connected yet — retry shortly. |
BiometricDisabled | Biometrics are not enabled for this application. |
BiometricMaxAttemptsReached | Maximum attempts for this call have been used. |
BiometricAlreadyInProgress | A verification is already running. |
RecipientNotConnected | The consumer’s device is not connected to the channel. |
InvalidRequest | The request was rejected as invalid. |
Implementation time
A typical end-to-end integration takes about 35 minutes.
| Task | Estimated time |
|---|---|
| Install the SDK | 2 min |
| Initialize the client | 2 min |
| Mint access tokens server-side | 10 min |
| Wire up branding callbacks | 3 min |
| Set up branding for a call | 3 min |
| Clear branding & add notes | 5 min |
| Open & close the data channel | 4 min |
| Trigger biometric verification | 3 min |
| Build the verification UI | 3 min |
Total: ~35 minutes