Skip to main content

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​

ParameterRequired?Description
is_appRecommended as 1 in an appDisplays the close button and enables app back handling.
langOptional; page language by defaultUI language: en, zh-cn, zh-tw, ja, ko, de, fr, pt, ru, es, vi, th, id, ms, or tl.
themeOptional; light by defaultlight, dark, or system.
directRequired as 1 for a direct conversationEnables direct-conversation mode; chatid is ignored without it.
chatidRequired for a direct conversationTarget conversation ID. chat_id is also accepted.
sbsRequired when binding a business userUnique user ID in your business system.
sbs_mmRequired when sbs is presentSignature for sbs.
ranstrRequired when a signature is usedRandom string included in the signature.
nameOptionalVisitor name.
nicknameOptionalVisitor nickname.
emailOptionalVisitor email address.
phoneOptionalVisitor phone number.
customer_remarkOptionalCustomer remark.
ext_fk_idOptionalExisting visitor ID used to continue historical conversations.
refererOptionalSource page URL. referer_url is also accepted.
source_titleOptionalSource page title.
new_message_sound_modeOptionalSet to native to let the app play new-message sounds; otherwise the page plays them.
sourceOptionalSet 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; iOS NSMicrophoneUsageDescription/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​

MethodWhen it is calledWhat the app should do
closeThe user taps the page close buttonClose the WebView or leave the current page.
downloadThe user saves an image, video, or fileDownload params.url, then report the result as described below.
new_messageA new message arrives and native sound is enabledPlay the notification sound. params may contain messageId or chatId.
android_recording_permissionAndroid recording startsRequest microphone permission and return { code: 1, data: { permission: true/false } }.
visitor_back_resultThe page responds to a back requestSee 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:

FieldDescription
urlFile URL.
typeimage, video, or another file type.
fileNameSuggested file name.
mimeTypeMIME type.
requestIdID 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' } }
}))