Build a mobile chat widget
Until native iOS and Android SDKs ship, embed the web widget inside a WebView. Works on iOS (WKWebView), Android (WebView), and React Native (react-native-webview). Pass data-fullscreen="true" so the widget renders without the floating launcher.
React Native
ChatScreen.tsx
// React Native + react-native-webview
import { WebView } from "react-native-webview";
const HTML = `<!doctype html><html><head><meta name="viewport" content="width=device-width,initial-scale=1" /></head>
<body style="margin:0">
<script>
(function(d,s,o){
var j=d.createElement(s);j.async=1;j.src='https://app.message.com/widget.js';
j.dataset.workspace=o;
j.dataset.fullscreen='true';
d.head.appendChild(j);
})(document,'script','YOUR_WORKSPACE_ID');
</script>
</body></html>`;
export default function ChatScreen() {
return (
<WebView
originWhitelist={["*"]}
source={{ html: HTML, baseUrl: "https://yourapp.local" }}
javaScriptEnabled
domStorageEnabled
/>
);
}iOS (Swift / WKWebView)
ChatViewController.swift
// iOS / Swift / WKWebView
import WebKit
let html = """
<!doctype html><html><head><meta name="viewport" content="width=device-width,initial-scale=1" /></head>
<body style="margin:0">
<script>
(function(d,s,o){
var j=d.createElement(s);j.async=1;j.src='https://app.message.com/widget.js';
j.dataset.workspace=o;
j.dataset.fullscreen='true';
d.head.appendChild(j);
})(document,'script','YOUR_WORKSPACE_ID');
</script>
</body></html>
"""
let webView = WKWebView(frame: .zero)
webView.loadHTMLString(html, baseURL: URL(string: "https://yourapp.local"))Android (Kotlin / WebView)
ChatActivity.kt
val html = """
<!doctype html><html><head><meta name="viewport" content="width=device-width,initial-scale=1" /></head>
<body style="margin:0">
<script>
(function(d,s,o){
var j=d.createElement(s);j.async=1;j.src='https://app.message.com/widget.js';
j.dataset.workspace=o;
j.dataset.fullscreen='true';
d.head.appendChild(j);
})(document,'script','YOUR_WORKSPACE_ID');
</script>
</body></html>
""".trimIndent()
val webView = findViewById<WebView>(R.id.webview)
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.loadDataWithBaseURL("https://yourapp.local", html, "text/html", "UTF-8", null)Identify the signed-in user
Bridge native to JS. iOS uses evaluateJavaScript, Android uses WebView.evaluateJavascript, React Native uses the injectJavaScript prop. Call Message.identify once the bridge is ready.
bridge.js
function identifyUser(user) {
if (window.Message) callIt();
else window.MessageOnReady = callIt;
function callIt() {
window.Message.identify({
email: user.email,
name: user.name,
userId: user.id,
userHash: user.userHash // computed on your server
});
}
}Push notifications
Two approaches:
- Server-side webhook + APNs / FCM. Subscribe to
message:receivedon the agent namespace from a small relay service, look up the visitor's device token, push. - Polling fallback. When the app opens, call
GET /api/v1/conversations?visitorId=Xfor unread.
Common pitfalls
- Cookies in WebView. The widget uses cookies to keep the visitor session stable. Enable
WKWebViewConfiguration.websiteDataStoreon iOS andCookieManager.setAcceptCookie(true)on Android. - App Store review. If you collect identifiable info inside chat, list message.com as a data sub-processor in your privacy disclosures.
- Background. When the app backgrounds, the WebView pauses. Messages received in the background should be delivered via push, not via the WebView itself.
- RN bundler. Some Metro configurations strip data attributes. Inject the snippet via
injectJavaScriptifsource.htmldoes not work.