Embeding in WebView (Android and iOS)
Overview
This guide explains how to embed the webchat in a WebView component of a native mobile application using the Native Host API.
The Native Host API replaces the PostMessage handshake. Instead of exchanging AWAITING_CONFIG / SET_CONFIG / START_CONVERSATION messages, the host application:
- creates the global
window.webchatobject with the configuration before the webchat page loads, - optionally adds an
onEventcallback to it to receive webchat events, - optionally calls methods that the webchat adds to the same object to control it at runtime.
The webchat is loaded directly (top-level) in the WebView — there is no iframe and no chat-ui-launcher script involved. The host application does not need to know any event names or message formats to start a conversation.
Available from v266.
The PostMessage mechanism remains fully supported. Existing integrations continue to work without changes, and both mechanisms can be used side by side.
How it works
The contract between the host application and the webchat is a single global object, window.webchat, in the WebView's JavaScript context:
| Member | Provided by | Purpose |
|---|---|---|
window.webchat.config | host, before the page loads | Configuration object, read once when the webchat starts |
window.webchat.onEvent(type, payload) | host, optional | Callback receiving webchat events |
window.webchat.updateConfig(), sendEvent(), ... | webchat, after it mounts | Methods for runtime control |
The host creates window.webchat with config (and optionally onEvent) before the webchat's JavaScript executes. The webchat then adds its methods to the same object — it never replaces it. How the host defines the object before load is platform-specific (see the examples below).
Reserved names: updateConfig, startConversation, sendEvent, destroy. Do not define them in your object; the webchat overwrites them.
URL to load
Load the following URL in the WebView:
https://webchat.app.chatbots.sentione.com/fullscreen/CHANNEL_ID
Replace CHANNEL_ID with the ID of the channel configured in the admin panel. The channel configuration (appearance, handover, translations) is applied automatically and window.webchat.config is merged on top of it.
Providing the configuration: window.webchat.config
window.webchat.configThe configuration object has the same shape as the initWebChat configuration used by the chat-ui-launcher script (see Configuration). A minimal configuration:
{
"session": {
"id": "unique-session-identifier"
},
"author": "John Doe",
"actionButtons": {
"conversationWindow": {
"minimize": false,
"resetAndMinimize": false
}
}
}session.id— see Important Note on Session Management.author— name of the user, shown in the conversation and passed to the bot.actionButtons.conversationWindow— in a WebView there is usually nothing to minimize to, so both buttons are typically disabled.
When window.webchat.config is present, the webchat applies it, starts the conversation automatically and does not wait for any PostMessage. The WEBCHAT_READY event is still emitted for compatibility.
The value is read once, when the webchat starts. To change the configuration later use window.webchat.updateConfig(...).
Controlling the webchat at runtime: window.webchat
window.webchatAfter the webchat has started, the following methods are added to window.webchat. Call them from native code with evaluateJavascript.
| Method | Description |
|---|---|
updateConfig(config) | Merges config into the current configuration (e.g. change author, language, appearance). |
startConversation() | Starts a new conversation. Not needed on load — the conversation starts automatically when config is provided. Use it to start again after the conversation was closed (CLOSE_CONVERSATION). |
sendEvent({ label, extraData }) | Sends an event message to the bot, e.g. to trigger a specific flow. Starts the conversation first if there is none. |
destroy() | Closes the conversation session and emits DESTROY_DONE. The page itself stays interactive — typing starts a brand-new conversation — so the host should hide or close the WebView on DESTROY_DONE. |
The methods exist only after the webchat has mounted. Use optional chaining (window.webchat?.updateConfig?.(...)) so that a call made before the page has finished loading is a no-op instead of an error.
Receiving events: window.webchat.onEvent
window.webchat.onEventIf the host defines window.webchat.onEvent(type, payload), the webchat calls it for every event it emits. type is the event name, payload is always a JSON string — JSON.parse(payload) returns the event data, or null when the event has no payload.
| Event | Payload | Description |
|---|---|---|
CHANNEL_CONFIG | channel configuration object | Sent first, when the channel configuration has been loaded (channel URLs only). |
WEBCHAT_READY | — | Configuration applied. Emitted on load and again after every updateConfig() / SET_CONFIG. |
CLIENT_MESSAGE_SENT | message text | The user sent a message. For event messages sent with sendEvent() the payload is null (no text). |
MESSAGE_RECEIVED | message object | A message from the bot or a live agent arrived. Bot messages contain responses[].text; agent messages contain text. |
CONVERSATION_END | — | The conversation is finished. |
CLOSE_CONVERSATION_WINDOW | — | The user clicked the close (X) icon. |
MINIMIZE_CONVERSATION_WINDOW | — | The user clicked the minimize icon. |
DESTROY_DONE | { requestSuccess, requestStatus, requestStatusText } | The webchat has been destroyed; the payload describes the result of the session-closing request. |
ANNOUNCEMENT_BANNER_LINK_CLICKED | { href, content, announcementBannerConfig } | The user clicked a link in the announcement banner. Emitted only when announcementBanner.markdownLinkBehavior is "callback"; with "new-tab" the link simply opens. |
CAROUSEL_BUTTON_CLICKED | { button } | The user clicked a carousel button of type notify_host. Buttons that open a URL emit nothing. |
Defining onEvent is optional. Without it the webchat works normally; the events are simply not delivered anywhere.
Example Android implementation
The example uses WebViewCompat.addDocumentStartJavaScript from androidx.webkit to define window.webchat before the page scripts run. Events are forwarded to a native object registered with addJavascriptInterface under a different name (webchatNative) — the interface must not be registered as webchat, because that would replace the shared object with the native proxy.
Add the dependency:
implementation("androidx.webkit:webkit:1.12.1")Make sure the manifest contains <uses-permission android:name="android.permission.INTERNET" /> and the activity has android:windowSoftInputMode="adjustResize" so the keyboard does not cover the message input.
package com.example.myapplication
import android.os.Bundle
import android.util.Log
import android.webkit.ConsoleMessage
import android.webkit.JavascriptInterface
import android.webkit.WebChromeClient
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewFeature
import com.example.myapplication.databinding.ActivityMainBinding
import org.json.JSONObject
import java.util.UUID
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
mediaPlaybackRequiresUserGesture = false
}
binding.webView.webChromeClient = object : WebChromeClient() {
override fun onConsoleMessage(message: ConsoleMessage): Boolean {
Log.d("WebView", "${message.message()} (${message.sourceId()}:${message.lineNumber()})")
return true
}
}
if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
WebViewCompat.addDocumentStartJavaScript(
binding.webView,
"""
window.webchat = {
config: ${buildHostConfig()},
onEvent: (type, payload) => webchatNative.onEvent(type, payload)
};
""".trimIndent(),
setOf("*")
)
} else {
Log.e("WebView", "DOCUMENT_START_SCRIPT not supported by this WebView")
}
binding.webView.addJavascriptInterface(WebchatNativeBridge(), "webchatNative")
binding.webView.loadUrl(WEBCHAT_URL)
}
private fun buildHostConfig(): String = JSONObject()
.put("session", JSONObject().put("id", sessionId()))
.put("author", "John Doe")
.put(
"actionButtons",
JSONObject().put(
"conversationWindow",
JSONObject().put("minimize", false).put("resetAndMinimize", false)
)
)
.toString()
private fun sessionId(): String =
getPreferences(MODE_PRIVATE).getString("webchat_session", null)
?: UUID.randomUUID().toString().also {
getPreferences(MODE_PRIVATE).edit().putString("webchat_session", it).apply()
}
inner class WebchatNativeBridge {
@JavascriptInterface
fun onEvent(type: String, payload: String) {
Log.d("WebView", "event $type: $payload")
when (type) {
"MESSAGE_RECEIVED" -> onMessageReceived(JSONObject(payload))
"CONVERSATION_END" -> runOnUiThread {
Toast.makeText(this@MainActivity, "Conversation ended", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun onMessageReceived(message: JSONObject) {
val text = message.optJSONArray("responses")?.optJSONObject(0)?.optString("text")
?: message.optString("text")
if (text.isNullOrBlank()) return
runOnUiThread {
Toast.makeText(this, "New message: $text", Toast.LENGTH_SHORT).show()
}
}
private fun updateWebchatConfig(config: JSONObject) {
binding.webView.evaluateJavascript("window.webchat?.updateConfig?.($config)") { result ->
Log.d("WebView", "updateConfig -> $result")
}
}
private fun sendWebchatEvent(label: String, extraData: JSONObject = JSONObject()) {
val payload = JSONObject().put("label", label).put("extraData", extraData)
binding.webView.evaluateJavascript("window.webchat?.sendEvent?.($payload)", null)
}
companion object {
private const val WEBCHAT_URL = "https://webchat.app.chatbots.sentione.com/fullscreen/CHANNEL_ID"
}
}Notes:
onEventis called on a background thread — userunOnUiThreadbefore touching the UI.- Build the configuration with
JSONObjectinstead of concatenating strings. The result is inserted into JavaScript as a literal, so values containing quotes or special characters are handled correctly. addDocumentStartJavaScriptis not available on very old system WebViews. CheckWebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)and fall back to the PostMessage integration when it returnsfalse.
Configure channel
Because the webchat is loaded directly in the WebView (not inside an iframe on a web page), it reports its own origin as the embedding domain. In the allowed domains field of the channel configuration add the WebChat URL used in the WebView, for example:
https://webchat.app.chatbots.sentione.com
Alternatively, the application can send a Referer header with its own domain when loading the URL (e.g. loadUrl(url, mapOf("Referer" to "https://your-app.example.com"))). In that case add that domain to the allowed domains instead.
Without a matching entry every request from the webchat is rejected with Domain ... is not allowed in this subscription.
Important Note on Session Management
The session.id field is crucial for proper webchat functionality.
- The session ID should be generated on the host side (your application).
- It is the host's responsibility to store and manage this session ID.
- The host should keep the session ID up to date and reset it when necessary (e.g. starting a new conversation, user logout).
Proper session management ensures conversation continuity and user experience consistency. A stable session ID across application restarts restores the conversation history.
Migrating from the PostMessage integration
| PostMessage integration | Native Host API |
|---|---|
Wait for AWAITING_CONFIG, send SET_CONFIG | Define window.webchat.config before load |
Wait for WEBCHAT_READY, send START_CONVERSATION | Not needed on load — the conversation starts automatically. window.webchat.startConversation() restarts it later (e.g. after CLOSE_CONVERSATION) |
SET_CONFIG at runtime | window.webchat.updateConfig(...) |
TRIGGER_BOT | window.webchat.sendEvent(...) |
DESTROY | window.webchat.destroy() |
window.addEventListener('message', ...) | window.webchat.onEvent(type, payload) |
Both mechanisms can coexist during migration — a SET_CONFIG message received after window.webchat.config is applied as a configuration update.
Troubleshooting
- The webchat shows the header but no message input:
window.webchat.configwas not defined before the page scripts ran. Make sure the script is injected at document start (addDocumentStartJavaScript), not after page load. - Requests fail with
Domain ... is not allowed in this subscription: add the webchat origin (or theRefererdomain) to the channel's allowed domains. window.webchat.updateConfigisundefined: the webchat has not mounted yet. Call the methods after the page has loaded and use optional chaining.- Events do not arrive in native code: check that
onEventis defined onwindow.webchatat document start and that the native interface / message handler (webchatNative) is registered beforeloadUrl/load. - Nothing works and the WebView is blank: check that JavaScript is enabled and, on Android, that the URL is reachable (cleartext HTTP is blocked by default — use HTTPS).
Using PostMessage instead
The webchat also accepts commands and emits events through the standard window.postMessage API. This mechanism keeps working unchanged and can be used instead of, or alongside, the Native Host API. Because the webchat is loaded top-level in the WebView, window.parent is the webchat window itself, so messages posted from native code are delivered to the webchat directly.
Messages in both directions have the same shape:
{ "type": "EVENT_TYPE", "payload": { } }Commands accepted by the webchat
| Type | Payload | Description |
|---|---|---|
SET_CONFIG | configuration object | Initializes or updates the configuration. Send it in response to AWAITING_CONFIG. |
START_CONVERSATION | — | Starts the conversation. Send it after WEBCHAT_READY. |
TRIGGER_BOT | { label, extraData } | Sends an event message to the bot. |
CLOSE_CONVERSATION | — | Closes and resets the current conversation. |
DESTROY | — | Destroys the webchat and closes the session. |
Events emitted by the webchat — the same list as for onEvent above, plus AWAITING_CONFIG, sent when the webchat is mounted and waiting for SET_CONFIG. AWAITING_CONFIG is not emitted when window.webchat.config is present.
Sending a command from native code:
val message = JSONObject()
.put("type", "SET_CONFIG")
.put("payload", JSONObject().put("session", JSONObject().put("id", sessionId())))
webView.evaluateJavascript("window.postMessage($message, '*')", null)Receiving events requires a message listener in the page that forwards them to native code, for example injected with addDocumentStartJavaScript:
window.addEventListener("message", (event) => {
if (event.data && event.data.type) {
webchatNative.onEvent(event.data.type, JSON.stringify(event.data.payload ?? null));
}
});The handshake order matters: wait for AWAITING_CONFIG before sending SET_CONFIG, and for WEBCHAT_READY before START_CONVERSATION. The Native Host API is the recommended option for new integrations because it removes this handshake and delivers events straight to native code.
Updated about 1 hour ago
