Embed the Visitor Chat in an App WebView
Open the visitor chat page in a WebView or WKWebView owned by your app without using the Visitor SDK. For a managed integration, see the Visitor SDK integration guide.
Open the page
Set url to the URL configured in the console. It must use HTTPS. Append your
business parameters as a query string after UTF-8 encoding them. Never put a
password, cookie, AppSecret, or long-lived token in a URL.
For an in-app experience, include is_app=1 so the visitor page displays its
close button:
<console-configured-URL>?is_app=1&lang=zh-cn&theme=system
Android:
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.loadUrl(buildVisitorUrl())
iOS:
let config = WKWebViewConfiguration()
config.preferences.javaScriptEnabled = true
webView.load(URLRequest(url: visitorURL))
Loading the URL is enough to start chatting. Inject the AppBridge described below when you need a close action, native downloads, native notification sounds, or Android recording permission.
Query parameters
| Parameter | Required? | Description |
|---|---|---|
is_app | Recommended as 1 in an app | Displays the close button and enables app back handling. |
lang | Optional; page language by default | UI language: en, zh-cn, zh-tw, ja, ko, de, fr, pt, ru, es, vi, th, id, ms, or tl. |
theme | Optional; light by default | light, dark, or system. |
direct | Required as 1 for a direct conversation | Enables direct-conversation mode; chatid is ignored without it. |
chatid | Required for a direct conversation | Target conversation ID. chat_id is also accepted. |
sbs | Required when binding a business user | Unique user ID in your business system. |
sbs_mm | Required when sbs is present | Signature for sbs. |
ranstr | Required when a signature is used | Random string included in the signature. |
name | Optional | Visitor name. |
nickname | Optional | Visitor nickname. |
email | Optional | Visitor email address. |
phone | Optional | Visitor phone number. |
customer_remark | Optional | Customer remark. |
ext_fk_id | Optional | Existing visitor ID used to continue historical conversations. |
referer | Optional | Source page URL. referer_url is also accepted. |
source_title | Optional | Source page title. |
new_message_sound_mode | Optional | Set to native to let the app play new-message sounds; otherwise the page plays them. |
source | Optional | Set to webview to hide the default Web back button. |
Do not send visitor_id. source=webview has the same effect as
is_app=1; use one of them rather than both.
WebView requirements
- Enable JavaScript and DOM Storage.
- Cookies and LocalStorage are persistent by default. Do not clear them unnecessarily, or the visitor identity may be lost.
- Load HTTPS only. Keep the system ATS/Cleartext settings at their secure defaults.
- For same-origin microphone and camera capture, declare the corresponding
system permissions (Android
RECORD_AUDIO/CAMERA; iOSNSMicrophoneUsageDescription/NSCameraUsageDescription). - Use the WebView file picker for attachments.
- Keep same-origin HTTPS navigation in the WebView and open cross-origin HTTPS URLs in the system browser.
AppBridge
The page calls the app with window.AppBridge.call(method, params, callback).
Inject the following script before each page loads (prefer atDocumentStart;
do not wait for onPageFinished):
(function () {
window.__TWT_VISITOR_BRIDGE_CONFIG__ = { newMessageSoundMode: 'web' };
window.AppBridge = {
_callbacks: {},
call: function (method, params, callback) {
var id = callback ? 'cb_' + Date.now() + '_' + Math.random().toString(16).slice(2) : null;
if (callback) this._callbacks[id] = callback;
var msg = JSON.stringify({ method: method, params: params || {}, callback: id });
if (window.AppBridgeChannel_native) {
window.AppBridgeChannel_native.postMessage(msg);
} else if (window.webkit && window.webkit.messageHandlers.AppBridgeChannel) {
window.webkit.messageHandlers.AppBridgeChannel.postMessage(msg);
}
return Promise.resolve({ status: 'sent', callbackId: id });
},
invokeCallback: function (id, result) {
var cb = this._callbacks[id];
if (cb) { cb(result); delete this._callbacks[id]; }
}
};
})();
On Android, register a JavascriptInterface named
AppBridgeChannel_native with a postMessage method. On iOS, register a
WKScriptMessageHandler named AppBridgeChannel.
Messages use this JSON shape:
{ "method": "...", "params": {}, "callback": "cb_..." }
Methods called by the page
| Method | When it is called | What the app should do |
|---|---|---|
close | The user taps the page close button | Close the WebView or leave the current page. |
download | The user saves an image, video, or file | Download params.url, then report the result as described below. |
new_message | A new message arrives and native sound is enabled | Play the notification sound. params may contain messageId or chatId. |
android_recording_permission | Android recording starts | Request microphone permission and return { code: 1, data: { permission: true/false } }. |
visitor_back_result | The page responds to a back request | See System back. |
Without the bridge, the close button has no effect, downloads use the browser, and the page plays its own notification sounds.
Report download results
The download parameters are:
| Field | Description |
|---|---|
url | File URL. |
type | image, video, or another file type. |
fileName | Suggested file name. |
mimeType | MIME type. |
requestId | ID of this download request. |
When the request includes a callback, invoke it after the download finishes:
window.AppBridge.invokeCallback(callbackId, {
code: 1,
data: { requestId: '...', status: 'completed' }
})
status is completed, failed, or cancelled. Use code: 0 for a failed
download.
To let the app play notification sounds, set newMessageSoundMode to
'native' in the injected configuration or add
new_message_sound_mode=native to the URL.
System back (optional)
When the user presses the system back button, ask the page first. It can close a preview or return from a conversation to the list:
window.dispatchEvent(new CustomEvent('twt:visitor-event', {
detail: { type: 'back', payload: {} }
}))
The page calls visitor_back_result within about 300 ms. If
params.consumed === true, the page handled the action; otherwise close the
WebView.
To change the theme while the page is running (only light or dark):
window.dispatchEvent(new CustomEvent('twt:visitor-event', {
detail: { type: 'themeChanged', payload: { theme: 'dark' } }
}))