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
| Field | Type | Description |
|---|---|---|
appid | string | Unique application ID. Production integrations should use lower-case appid. |
Optional Configuration
| Field | Type | Default | Description |
|---|---|---|---|
asyncInit | boolean | false | Whether to skip automatic init() |
language | string | Browser language fallback | Initial language. See Language Configuration |
theme | 'light' | 'dark' | 'system' | light | Initial theme |
launcher | 'default' | 'custom' | default | Launcher rendering mode |
zIndex | number | Determined by host-sdk default styles | Widget container z-index |
sbs | string | None | Business user identifier |
sbs_mm | string | None | Business user signature |
ranstr | string | None | Signature random string |
name | string | None | Visitor name |
nickname | string | None | Visitor nickname |
email | string | None | Visitor email |
phone | string | None | Visitor phone number |
referer | string | Current page URL | Referrer URL |
source_title | string | Current page title | Referrer 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 Value | Language |
|---|---|
en | English |
zh-cn | 简体中文 |
zh-tw | 繁體中文 |
ja | 日本語 |
ko | 한국어 |
de | Deutsch |
fr | Français |
pt | Portuguê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
| Function | Description | Available Before Initialization |
|---|---|---|
on(event, handler) | Registers an event listener | Supported by the minimal installation stub and queued |
once(event, handler) | Registers a one-time event listener | Depends on whether the installation stub provides it |
off(event, handler?) | Removes an event listener | Depends on whether the installation stub provides it |
call(method, payload?) | Calls a widget method | Recommended after initialized |
get(getter) | Reads state synchronously | Only after initialization |
init() | Initializes manually | Available after core.js loads |
login(...) | Convenience method for business user login | After initialization |
setLanguage(locale) | Convenience method for call('set_language', locale) | After initialization |
scriptTagVersion() | Returns the current SDK script version | Available 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:
| Value | Description |
|---|---|
light | Light theme |
dark | Dark theme |
system | Follow 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:
| Field | Type | Description |
|---|---|---|
visibility | 'maximized' | 'minimized' | 'hidden' | Current visibility |
sdk_info
Reads SDK version and runtime information.
const sdkInfo = window.TWTChatWidget.get("sdk_info");
Returns:
| Field | Type | Description |
|---|---|---|
version | string | host-sdk runtime version |
scriptTagVersion | string | Script 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
| Event | Payload | Description |
|---|---|---|
initialized | None | host-sdk init() completed |
iframe_loaded | { surface: string } | iframe DOM load completed, used only for diagnostics |
bridge_ready | None | Runtime bridge handler is registered and can accept commands |
runtime_mounted | None | Vue runtime has mounted |
visitor_auth_ready | See below | Visitor token has been resolved or restored |
runtime_activated | None | First activation completed and the initial session list has been resolved |
destroyed | None | Widget 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.
| Field | Type | Description |
|---|---|---|
bs | string | Visitor identity hint |
bs_mm | string | Visitor identity signature hint |
fk_id | string | Legacy customer-page compatibility field |
fkid | string | Runtime / backend record field, semantically equivalent to fk_id |
hasToken | boolean | Whether the current runtime already has a usable visitor token |
source | 'initial' | 'resolved' | Source |
visitorUid | string | Runtime 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
| Event | Payload | Description |
|---|---|---|
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_closed | See below | Visitor authentication was closed by the server |
bootstrap_failed | See below | Required embedded bootstrap flow failed |
unread_count_changed | { count: number } | Unread count changed |
Allowed visibility values:
| Value | Description |
|---|---|
maximized | Panel expanded |
minimized | Only the launcher is visible |
hidden | Both launcher and panel are hidden |
Allowed status values:
| Value | Description |
|---|---|
disconnected | Not connected or disconnected |
connecting | Connecting |
connected | Connected |
reconnecting | Reconnecting |
auth_closed
After authentication closes, the host-sdk hides the widget and dispatches this event to the host page.
Common payload fields:
| Field | Type | Description |
|---|---|---|
message | string | Description of why authentication closed |
reason | string | Close reason |
source | string | Source, such as http or a WebSocket source |
clearToken | boolean | Whether the local token should be cleared |
closeAuthType | string | Server-side close type |
closeCode | number | Server-side close code |
terminal | string | Terminal source |
bootstrap_failed
Triggered when a required embedded bootstrap flow fails. The host-sdk hides and cleans up the current widget.
Common payload fields:
| Field | Type | Description |
|---|---|---|
reason | 'domain_not_whitelisted' | 'login_failed' | 'invalid_bootstrap' | 'timeout' | string | Failure reason |
source | string | Failure 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");
});