Skip to main content

JavaScript API

Overview

Use the ... Widget JavaScript API to control the visitor-side widget from the host page, including opening or minimizing the panel, listening for initialization and unread-count changes, switching language or theme, and logging in a business user.

Install the SDK

Installation Snippet

<!-- Start of ... code -->
<script>
window.TWTChatWidget = window.TWTChatWidget || {
_q: [],
on: function () {
this._q.push(["on", Array.prototype.slice.call(arguments)]);
},
};

window.__twt = {
appid: "YOUR_APPID",
};

(function (n, t) {
var e = {
init: function () {
var n = t.createElement("script");
n.async = !0;
n.type = "text/javascript";
n.src = ".../install/core.js?version=v1.2";
t.head.appendChild(n);
},
};
e.init();
})(window, document);
</script>
<!-- End of ... code -->

Configuration Options

All configuration is written to window.__twt and read during the core.js bootstrap phase.

Required Configuration

FieldTypeDescription
appidstringUnique application ID. Production integrations should use lower-case appid.

Optional Configuration

FieldTypeDefaultDescription
asyncInitbooleanfalseWhether to skip automatic init()
languagestringBrowser language fallbackInitial language. See Language Configuration
theme'light' | 'dark' | 'system'lightInitial theme
launcher'default' | 'custom'defaultLauncher rendering mode
zIndexnumberDetermined by host-sdk default stylesWidget container z-index
sbsstringNoneBusiness user identifier
sbs_mmstringNoneBusiness user signature
ranstrstringNoneSignature random string
namestringNoneVisitor name
nicknamestringNoneVisitor nickname
emailstringNoneVisitor email
phonestringNoneVisitor phone number
refererstringCurrent page URLReferrer URL
source_titlestringCurrent page titleReferrer title

Example:

window.__twt = {
appid: "YOUR_APPID",
language: "en",
theme: "dark",
launcher: "custom",
zIndex: 9999,
};

Language Configuration

window.__twt.language and window.TWTChatWidget.setLanguage(locale) use the same runtime locale set. Integrators should pass the standard values in the table below. Letter case and common regional variants are normalized by the SDK / runtime.

Standard ValueLanguage
enEnglish
zh-cn简体中文
zh-tw繁體中文
ja日本語
ko한국어
deDeutsch
frFrançais
ptPortuguês
ruРусский

When no language is configured explicitly, the SDK selects the first supported language from navigator.languages / navigator.language. If nothing matches, it falls back to en. Unsupported explicit values are not preserved as runtime locales, so host pages should only pass values from the supported list.

Initialization Timing

By default, core.js automatically calls init() and creates the iframe after it loads.

To delay widget iframe creation, set asyncInit: true, then initialize manually at the appropriate time:

<script>
window.TWTChatWidget = window.TWTChatWidget || {
_q: [],
on: function () {
this._q.push(["on", Array.prototype.slice.call(arguments)]);
},
};

window.__twt = {
appid: "YOUR_APPID",
asyncInit: true,
};

window.TWTChatWidget.on("initialized", function () {
console.log("Widget initialized");
});

(function (d) {
var sdk = d.createElement("script");
sdk.async = true;
sdk.src = ".../install/core.js?version=v1.2";
sdk.onload = function () {
document
.querySelector("#contact-us")
.addEventListener("click", function () {
window.TWTChatWidget.init();
});
};
d.head.appendChild(sdk);
})(document);
</script>

init() is idempotent, so repeated calls do not create duplicate instances. When using asyncInit, call init() only after core.js has loaded. get() is synchronous and must be used after init() completes; otherwise it throws an error.

Entry Contract

window.TWTChatWidget

window.TWTChatWidget is the unified public API entry point. Host pages should use it to call methods, register events, and read state:

window.TWTChatWidget.on("initialized", function () {
// Open the widget automatically after the SDK widget has loaded.
window.TWTChatWidget.call("maximize");
});

Core Functions

FunctionDescriptionAvailable Before Initialization
on(event, handler)Registers an event listenerSupported by the minimal installation stub and queued
once(event, handler)Registers a one-time event listenerDepends on whether the installation stub provides it
off(event, handler?)Removes an event listenerDepends on whether the installation stub provides it
call(method, payload?)Calls a widget methodRecommended after initialized
get(getter)Reads state synchronouslyOnly after initialization
init()Initializes manuallyAvailable after core.js loads
login(...)Convenience method for business user loginAfter initialization
setLanguage(locale)Convenience method for call('set_language', locale)After initialization
scriptTagVersion()Returns the current SDK script versionAvailable after core.js loads

scriptTagVersion() returns the same value as get('sdk_info').scriptTagVersion: the snippet version from the installation script. The only difference is timing. scriptTagVersion() can be called after core.js loads, while get('sdk_info') requires init() to complete.

Methods

maximize

Expands the chat panel.

window.TWTChatWidget.call("maximize");

minimize

Minimizes the chat panel and keeps only the built-in launcher visible.

window.TWTChatWidget.call("minimize");

hide

Hides the entire widget. The iframe remains mounted.

window.TWTChatWidget.call("hide");

show

Restores the widget from hidden state to minimized state.

window.TWTChatWidget.call("show");

toggle

Toggles between expanded and minimized states.

window.TWTChatWidget.call("toggle");

destroy

Destroys the host-side iframe, event listeners, and marketing-card surface.

window.TWTChatWidget.call("destroy");

destroy is a host-side API and does not require the runtime to confirm destruction first.

login

Reloads the iframe with a new business user identity.

Recommended convenience method:

window.TWTChatWidget.login(
"session_token_123", // sbs, business user identifier
"signature_abc", // sbs_mm, signature
"random_xyz", // ranstr, signature random string
"John Smith", // name, optional
"John", // nickname, optional
"john.smith@example.com", // email, optional
"13800138000", // phone, optional
);

You can also call the method directly:

window.TWTChatWidget.call("login", {
sbs: "session_token_123",
sbs_mm: "signature_abc",
ranstr: "random_xyz",
name: "John Smith",
nickname: "John",
email: "john.smith@example.com",
phone: "13800138000",
});

sbs, sbs_mm, and ranstr are required signature fields. If any of them are missing, the runtime rejects the login command.

set_language

Switches the runtime language.

window.TWTChatWidget.call("set_language", "en");

Convenience form:

window.TWTChatWidget.setLanguage("en");

Supported language values match the initialization configuration. See Language Configuration. set_language normalizes the input to a runtime locale first, then syncs the launcher and panel. If the current runtime session rejects the language switch, the panel copy will not update. Switching to an unsupported language does not add a language pack, so host pages should only pass values from the supported list.

set_theme

Switches the theme.

window.TWTChatWidget.call("set_theme", "dark");

Allowed values:

ValueDescription
lightLight theme
darkDark theme
systemFollow system settings

Getters

get() is synchronous and does not wait for the runtime queue. Call it after initialized.

state

Reads the current visibility state.

const state = window.TWTChatWidget.get("state");
console.log(state.visibility);

Returns:

FieldTypeDescription
visibility'maximized' | 'minimized' | 'hidden'Current visibility

sdk_info

Reads SDK version and runtime information.

const sdkInfo = window.TWTChatWidget.get("sdk_info");

Returns:

FieldTypeDescription
versionstringhost-sdk runtime version
scriptTagVersionstringScript version
compatibilityMode'off' | 'livechat'Compatibility mode. Currently returns off
transport'ws' | 'http-polling' | 'mixed'Currently returns mixed, meaning host bridge plus runtime WebSocket

unread_count

Reads the currently cached unread count.

const count = window.TWTChatWidget.get("unread_count");

Before the runtime sends unread_count_changed, the default value is 0.

theme

Reads the current SDK-side theme.

const theme = window.TWTChatWidget.get("theme");

Returns light, dark, or system.

Events

Listen for events with TWTChatWidget.on(event, handler).

window.TWTChatWidget.on("visibility_changed", function (payload) {
console.log(payload.visibility);
});

Keep a reference to the same handler when you need to unsubscribe:

function handleVisibilityChanged(payload) {
console.log(payload.visibility);
}

window.TWTChatWidget.on("visibility_changed", handleVisibilityChanged);
window.TWTChatWidget.off("visibility_changed", handleVisibilityChanged);

Lifecycle Events

EventPayloadDescription
initializedNonehost-sdk init() completed
iframe_loaded{ surface: string }iframe DOM load completed, used only for diagnostics
bridge_readyNoneRuntime bridge handler is registered and can accept commands
runtime_mountedNoneVue runtime has mounted
visitor_auth_readySee belowVisitor token has been resolved or restored
runtime_activatedNoneFirst activation completed and the initial session list has been resolved
destroyedNoneWidget has been destroyed

initialized and bridge_ready do not mean the WebSocket is connected, and they do not mean authentication has completed. To track connection status, listen for connection_state_changed or session_connected.

visitor_auth_ready

The visitor identity is ready. The payload may include identity hints that can be written back.

FieldTypeDescription
bsstringVisitor identity hint
bs_mmstringVisitor identity signature hint
fk_idstringLegacy customer-page compatibility field
fkidstringRuntime / backend record field, semantically equivalent to fk_id
hasTokenbooleanWhether the current runtime already has a usable visitor token
source'initial' | 'resolved'Source
visitorUidstringRuntime internal visitor uid, not equivalent to fk_id

The host-sdk writes ITP identity back to the current storage strategy based on this event. Integrators can also listen to it and sync the identity to their own business storage:

window.TWTChatWidget.on("visitor_auth_ready", function (payload) {
if (payload.bs && payload.bs_mm) {
localStorage.setItem(
"twt_visitor_identity",
JSON.stringify({
bs: payload.bs,
bs_mm: payload.bs_mm,
fk_id: payload.fk_id || payload.fkid,
}),
);
}
});

State Events

EventPayloadDescription
visibility_changed{ visibility }Visibility changed
connection_state_changed{ status }Connection status changed
session_connected{ status: 'connected' }WebSocket connected
session_disconnected{ status }WebSocket disconnected or reconnecting
auth_closedSee belowVisitor authentication was closed by the server
bootstrap_failedSee belowRequired embedded bootstrap flow failed
unread_count_changed{ count: number }Unread count changed

Allowed visibility values:

ValueDescription
maximizedPanel expanded
minimizedOnly the launcher is visible
hiddenBoth launcher and panel are hidden

Allowed status values:

ValueDescription
disconnectedNot connected or disconnected
connectingConnecting
connectedConnected
reconnectingReconnecting

auth_closed

After authentication closes, the host-sdk hides the widget and dispatches this event to the host page.

Common payload fields:

FieldTypeDescription
messagestringDescription of why authentication closed
reasonstringClose reason
sourcestringSource, such as http or a WebSocket source
clearTokenbooleanWhether the local token should be cleared
closeAuthTypestringServer-side close type
closeCodenumberServer-side close code
terminalstringTerminal source

bootstrap_failed

Triggered when a required embedded bootstrap flow fails. The host-sdk hides and cleans up the current widget.

Common payload fields:

FieldTypeDescription
reason'domain_not_whitelisted' | 'login_failed' | 'invalid_bootstrap' | 'timeout' | stringFailure reason
sourcestringFailure source
mode'embedded'Current runtime mode

This event only means embedded startup failed. It is not used for ordinary in-session HTTP 404 responses.

Common Scenarios

The examples below assume that the SDK has already been installed as described above.

Open the Window After Initialization

window.TWTChatWidget.on("initialized", function () {
window.TWTChatWidget.call("maximize");
});

Custom Entry Button

<button id="open-chat">Contact support</button>
<button id="close-chat">Minimize support</button>

<script>
window.__twt = {
appid: "YOUR_APPID",
launcher: "custom",
};

document.querySelector("#open-chat").addEventListener("click", function () {
window.TWTChatWidget.call("maximize");
});

document.querySelector("#close-chat").addEventListener("click", function () {
window.TWTChatWidget.call("minimize");
});
</script>
<script
src=".../install/core.js?version=v1.2"
async
></script>

launcher: 'custom' disables the built-in launcher iframe. In this mode, the host page must provide its own entry button.

Show a Custom Consultation Popup on Built-in Launcher Hover

  • Configure with window.__twt
  • Use window.TWTChatWidget.on("initialized", ...) for readiness events
  • Open chat with window.TWTChatWidget.call("maximize")
  • Select the built-in launcher with iframe[data-twt-widget-surface="launcher"]
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>... Widget Demo</title>
<style>
#my-custom-popup {
position: fixed;
right: 20px;
bottom: 100px;
z-index: 2147483647;
width: 280px;
padding: 20px;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
pointer-events: none;
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 16px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
opacity: 0;
transform: translateY(15px);
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
}

#my-custom-popup.show {
pointer-events: auto;
opacity: 1;
transform: translateY(0);
}

#my-custom-popup .btn-consult {
display: block;
padding: 10px;
font-size: 14px;
font-weight: 500;
color: #fff;
text-align: center;
text-decoration: none;
margin-bottom: 8px;
background: #2563eb;
border-radius: 8px;
transition:
background 0.2s,
transform 0.1s;
}

#my-custom-popup .btn-consult:hover {
background: #1d4ed8;
}

#my-custom-popup .btn-consult:active {
transform: scale(0.98);
}
</style>
</head>
<body>
<div id="my-custom-popup">
<div
style="font-size: 16px; font-weight: 600; color: #1e293b; margin-bottom: 8px"
>
Hi, welcome to chat with us.
</div>
<div
style="font-size: 13px; color: #64748b; line-height: 1.5; margin-bottom: 16px"
>
We are ready to help. Click the button below to start a live conversation.
</div>
<div style="display: flex; flex-direction: column">
<a href="javascript:void(0)" class="btn-consult" id="btn-start-chat"
>Start chat</a
>
<div style="font-size: 11px; text-align: center; color: #94a3b8">
Service hours: 9:00 - 18:00
</div>
</div>
</div>

<!-- Start of ... code -->
<script>
window.TWTChatWidget = window.TWTChatWidget || {
_q: [],
on: function () {
this._q.push(["on", Array.prototype.slice.call(arguments)]);
},
};

window.__twt = {
appid: "YOUR_APPID",
launcher: "default",
};

(function (n, t) {
var e = {
init: function () {
var n = t.createElement("script");
n.async = !0;
n.type = "text/javascript";
n.src = ".../install/core.js?version=v1.2";
t.head.appendChild(n);
},
};
e.init();
})(window, document);

(function () {
var hoverTimer = null;
var isHovered = false;
var popup = document.getElementById("my-custom-popup");

function showPopup() {
if (popup) {
popup.classList.add("show");
}
}

function hidePopup() {
if (popup) {
popup.classList.remove("show");
}
}

function bindPopupHover(launcherFrame) {
if (!launcherFrame || !popup) {
return;
}

function onEnter() {
isHovered = true;
if (hoverTimer) {
clearTimeout(hoverTimer);
}
showPopup();
}

function onLeave() {
isHovered = false;
if (hoverTimer) {
clearTimeout(hoverTimer);
}
hoverTimer = setTimeout(function () {
if (!isHovered) {
hidePopup();
}
}, 200);
}

launcherFrame.addEventListener("mouseenter", onEnter);
launcherFrame.addEventListener("mouseleave", onLeave);

popup.addEventListener("mouseenter", function () {
isHovered = true;
if (hoverTimer) {
clearTimeout(hoverTimer);
}
});
popup.addEventListener("mouseleave", onLeave);

var startChatButton = document.getElementById("btn-start-chat");
if (startChatButton) {
startChatButton.addEventListener("click", function () {
hidePopup();
window.TWTChatWidget.call("maximize");
});
}
}

window.TWTChatWidget.on("initialized", function () {
var launcherFrame = document.querySelector(
'iframe[data-twt-widget-surface="launcher"]',
);
bindPopupHover(launcherFrame);
});
})();
</script>
<!-- End of ... code -->
</body>
</html>

If launcher: "custom" is configured, the host-sdk will not create the built-in launcher iframe, so the hover target above will not exist. In that case, use a button or entry element provided by the host page.

Sync Unread Count to the Page Title

var originalTitle = document.title;

window.TWTChatWidget.on("unread_count_changed", function (payload) {
document.title =
payload.count > 0
? "(" + payload.count + ") " + originalTitle
: originalTitle;
});

Observe Connection Status

window.TWTChatWidget.on("connection_state_changed", function (payload) {
console.log("connection status:", payload.status);
});

window.TWTChatWidget.on("session_connected", function () {
console.log("session connected");
});

window.TWTChatWidget.on("session_disconnected", function (payload) {
console.log("session disconnected:", payload.status);
});

Switch Theme

window.TWTChatWidget.on("initialized", function () {
window.TWTChatWidget.call("set_theme", "dark");
});

Switch Language

window.TWTChatWidget.on("initialized", function () {
window.TWTChatWidget.setLanguage("en");
});