ChatWidgetSDK
A framework-agnostic JavaScript SDK that handles all chat communication (HTTP bot + WebSocket live-agent) so you can focus purely on building the Ul.
Installation
Use in a webpage via script tag:
<script src="https://https://dc-sdk.cloudagent.ozonetel.com/chat-widget-sdk.js"></script>
<!-- ChatWidgetSDK is now available as a global variable →→ >Initialisation
const sdk = ChatWidgetSDK.init ({
// Required
DID: 'YOUR_DID', // Digital campaign number
API_KEY: 'YOUR_API_KEY', // Authentication key
// Optional - caller info sent in every request
userDetails: {
id:
phone_number:
email:
name:
},
// Optional - arbitrary key/value pairs forwarded to the bot on every request
additionalData: I},
});ChatWidgetSDK.init() returns a configured SDK instance. Keep it in a module-level variable.
Configuration reference
User-facing (pass to ChatWidgetSDK.init())
| Key | Type | Required | Description |
|---|---|---|---|
| DID | string | yes | Digital campaign number |
| API_KEY | string | yes | Authentication key |
| userDetails.id | string | no | Caller ID |
| userDetails. phone_number | string | no | Caller phone |
| userDetails. email | string | no | Caller email |
| userDetails.name | string | no | Caller name |
| additionalData | object | no | Arbitrary data forwarded t o bot on every request |
Sending messages
All send methods are async and return Promise<void>.
Plain text
await sdk.sendMessage('Hello, I need help');Quick reply
/ Used to answer a 'question' message - pass any text as id and title
await sdk.sendReply({ id: 'user answer', title: 'user answer' });Menu item selection
await sdk.sendSelectedMenu({ nodeId: 'node_42', description: 'Check my order statusForm submission
await sdk.submitForm(
{ name: 'Alice' , email: '[email protected]' }, // key/value pairs
['Name: Alice', Email: [email protected]'l // display labels shown as use
) ;File / media upload
'file' is a browser File object (from <input type="file"> or drag-and-drop)
await sdk.sendMutilMediaMessage(file);sdk.on( 'uploadProgress', ({ inProgress }) => showSpinner(inProgress));Fallback message
Send a plain-text fallback when no structured input fits (e.g. unrecognised command):
await sdk.sendFallback('I did not understand that');End chat (user-initiated disconnect)
await sdk.endChat() ;Events reference
Subscribe with sdk.on(event, handler), unsubscribe with sdk.off(event, handler).
Event | Payload | When fired |
|---|---|---|
|
| New message from bot or agent |
|
| Message sent b y the local user |
|
| Live agent joined via WebSocket (after CCTransfer) |
|
| Agent started or stopped typing |
|
| Agent ended the chat from their side |
|
| Chat session fully ended |
|
| Brand-new session created |
|
| Existing session found in localStorage |
|
| Session cleaned u p |
|
| Bot returned a persistent menu |
|
| File upload started / finished |
|
| Status messages (e.g. "Getting agent for you...") |
|
| Network o r parsing error |
|
| Server settings applied successfully |
|
| Raw HTT request sent / response received - intended for dev tooling only |
debug event usage:
sdk.on(' debug', ({ action, data}) => {
if (action === 'request') console.log('->", data);
if (action === 'request') console.log('<-", data);
});Message object shapes
Every message arrives through the 'message' o r 'userMessage' event. The type field tells you exactly how to render it.
Common fields (all messages)
{
author: 'bot' | 'agent' | 'user',
type: string, // see types below
content: any, // string for most types; object for interactive
source: string, // media URL - empty string when not applicable
timestamp: string, // locale time string, e.g. "10:32:15 AM"
// bot / agent only
agentName: string, // agent display name (agent messages only, othe
showActionControls: boolean, // true → render action UI (text input, choices,
}type: 'text'
Plain text message from bot or agent.
{
author: 'bot' 'agent'
type: 'text'
content: 'Hello! How can I help you today?', // may contain HTML
source: '',
timestamp: "10:32:15 AM'
showActionControls: false,
}Render:
// content may contain HTML - use innerHTML
element.innerHTML = msg.content;type: 'question'
Bot asks a question. May include predefined reply options. Your Ul should disable the main text input while this message is shown — re-enable i t after the user taps a reply button or submits a free-text answer.
{
author: 'bot'
type: 'question',
content: 'Please enter your order number',
source:
timestamp: "10:32:20 AM'
showActionControls: true,
}Render:
// 1. Show the question text
renderText (msg.content);
// 2. Show a free-text input for the user's answer
if (msg.showActionControls) {
const input = createTextInput('Type your answer...');
input.onsubmit = async (text) = > {
await sdk.sendReply({ id: text, title: text });
;}
}type: 'interactive'
Rich interactive message - button list, scrollable menu, or carousel. Your Ul should disable the main text input until the user makes a selection.
{
author: 'bot',
type: 'interactive'
content: { interactive: {/* see sub-types below */ } },
source:
timestamp: '10:32:25 AM',
showActionControls: true,
}Access the interactive data via msg.content.interactive.
Sub-type: button
msg.content.interactive = {
type: 'button'
header: { type: 'text', body: 'Choose an option' }, // optional
title: 'What would you like to do?', // optional body text
choices: [
{ id: 'track', title: 'Track my order' },
{ id: 'return', , title: 'Return an item' },
{ id: 'support', title: 'Talk to support' },
],
}Render:
const { header, title, choices } = msg.content.interactive;
if (header?.type === 'text') renderHeader (header.body);
if (title) renderBodyText (title);
choices. forEach(choice => {
const btn = createButton(choice.title);
btn.onclick = async () = > await sdk.sendReply(choice);
});Sub-type: list
msg.content.interactive = {
type: 'list',
header: { type: 'text', body: 'Main Menu' }, // optional
title: 'Select a category', // optional
sections: [
{
title: 'Orders',
choices: [
{ id: 'track' title: 'Track Order', description: 'Check delivery state' },
{ id: 'cancel' title: 'Cancel Order', description: 'Cancel a recent order' },
],
},
{
title: 'Support',
choices: [
{ id: "faq', title: 'FAQs', description: 'Common questions' },
( id: 'agent', title: 'Live Agent', description: 'Chat with a person' },
],
},
],
}Render:
sections.forEach(section => {
rendersectionheader (section.title);
section.choices.forEach(choice => {
const item = createListItem(choice.title, choice.description);
item.onclick = async () => await sdk.sendReply(choice);
}) ;
}) ;Sub-type: carousel
Cards that share a common set of action buttons.
msg.content.interactive ={
type: 'carousel',
title: 'Choose a plan', // optional heading
data: {
data: {
options: [ // shared buttons shown on every card
{ id: 'select_plan', title: 'Select this plan' },
],
values: [ // the cards
{ title: 'Starter', description: '$9/mo', image: 'https://...' },
{ title: 'Growth', description: '$29/mo' , image: 'https://..'},
{ title: 'Enterprise', description: 'Custom' },
],
},
},
}Render:
const { options, values } = msg.content.interactive.data.data;
let currentIndex = 0;
function showCard (i) {
const card = values [i];
renderCardContent(card.image, card.title, card.description);
options.forEach(opt = > {
addButton(opt.title, async () = {
await sdk.sendReply({ id: opt.id, title: card.title || opt.title });
}) ;
}) ;
}
showCard(currentIndex);
// prev/next controls to change currentIndex and re-rendertype: "form'
Bot requests structured data from the user.
msg.content is the servers body object — form definition lives under msg.content.data
{
author: 'bot',
type: "form',
content: {
data: {
title: 'Please Enter Details', // form heading
subtitle: 'sub',
label: 'FormNode',
type: 'Form',
fields: [
{ id: '×6497', key: 'custName', label: "Name', type: 'dropdown', required
{ id: 'd@jk', key: 'Id', label: 'City', type: 'dropdown', required
{ id: 'ronli', key: 'custPhonel', label: 'Phone', type: "phone', reruired
{ id: 'em9b7', key: 'custmail', label: 'Email', type: 'email', reruired
],
responseVariable: "',
},
source: '',
timestamp: '10:32:30 AM',
showActionControls: false,
}Field types: text • email • phone (render as tel) • dropdown (options = comma-separated string)
Render:
const { title, fields } = msg.content.data;
const formData = {};
// render title
renderHeading(title);
fields.forEach (field) => {
let input;
if (field.type === 'dropdown') {
const opts = field.options.split(',') map(s = > s.trim());
input = createSelect (field.label, opts, field.required);
} else {
const inputType = field.type === 'phone' ? 'tel' : field.type;
input = createInput (field.label, inputType, field.required);
}
input.onchange = (val) = formData[field.key] = val; // key is field.key, NOT f
}) ;
submitBtn.onclick = async () => {
const labels = fields.map((f) = > '${f.label}: ${formData[f.key]}');
await sdk.submitForm(formData, labels);
};type: 'image'
Image message from bot or agent.
{
author: 'bot' | 'agent',
type: 'image'
content: 'Optional caption text' // may be empty
source: 'https://example.com/photo.jpg',
timestamp: "10:32:35 AM',
showActionControls: false,
}Render: <img src={msg.source} alt={msg.content} />
type: 'audio'
Audio message from bot or agent.
{
author: 'bot' | 'agent',
type: 'audio',
content: '',
source: 'https://example.com/clip.mp3',
timestamp: '10:32:40 AM',
showActionControls: false,
}**Render: ** <audio src={msg. source} controls />
type: 'video'
Video message from bot or agent.
{
author: 'bot' | 'agent',
type: 'video',
content: 'Optional caption',
source: 'https://example.com/cl1p.mp4',
timestamp: '10:32:45 AM',
showActionControls: false,
}Render: <video src={msg. source} controls />
type: 'document'
File attachment from bot or agent.
{
author: 'bot' | 'agent',
type: 'document',
content: 'filename.pdf', // display name
source: 'https://example.com/filename.pdf',
timestamp: "10:32:50 AM',
showActionControls: false,
}Render: <a href={msg. source} target="_blank"> {msg.content}</a>
MIME-typed media (agent messages via WebSocket)
When an agent sends a file, the type field is the raw MIME type string, not a simplified label.
{
author: 'agent'
type: 'image/jpeg' | 'image/png' | 'audio/mpeg' | 'video/mp4'
| 'application/pdf' | 'application/sword' | ...,
content: 'original-filename.jpg', // file name
source: 'https://cdn.example.com/file.jpg',
agentName: 'John',
timestamp: "10:33:00 AM',
showActionControls: false,
}Detection pattern:
if (msg.type.startsWith('image/')) renderImage (msg.source, msg.content);
else if (msg.type.startswith('audio/')) renderAudio(msg.source) ;
else if (msg.type.startsWith('video/')) renderVideo(msg.source) ;
else if (msg.type.startsWith('application/')) renderDocument(msg.source, msg.content) ;User message (from 'userMessage' event)
{
author: 'user',
type: 'text' | 'image' | 'audio' | 'video' | 'document',
content: 'Hello there', // text content or filename for media
source: '', // uploaded file URL (media only)
timestamp: '10:33:05 AM',
}Info message (from 'info' event)
System status messages — not part of the chat message list but useful for banners/toasts.
{
infoType: 'success' | 'error' | 'info' | 'warning',
content: 'Getting agent for you...',
}Common content values:
| content | when |
|---|---|
'Getting agent for you...' | CCTransfer received - WebSocket connecting |
'Agent connected: <name>' | Agent joined the WebSocket |
'Agent ended Chat' | Agent disconnected from their side |
'Chat Disconnected!!' | Server sent disconnect signal via HTTP |
'Chat Disconnected' | Server sent disconnect signal via WebSocket |
Updated 28 minutes ago
