Skip to content

Add chat to your app ​

Besides your website, you can talk to customers in your own app. Add a "Contact us" button that opens a full-page chat hosted by SealReply. There is no SDK to install, and it works with iOS, Android, Flutter and React Native. Messages, translation, the pre-chat form, business hours, the look and every other setting work as in the widget on your website, and agents still answer in the inbox.

  1. First verify your domain, as in Install the chat. Sites that are not verified cannot turn it on.

  2. Open Sites → your site → Install and, on the Use in your app card, click Turn on app chat.

  3. Copy the chat page link:

    text
    https://chat.sealreply.com/sk_…

    sk_… is your site key, the same as data-site-key in your website's embed code.

The switch is off by default. Until you turn it on, the link only says the chat is not available, so nobody who learns your site key can pass off a chat page as yours.

2. Open it in your app ​

The system's in-app browser works best: SFSafariViewController on iOS, Custom Tabs on Android, inAppBrowserView in Flutter. Images, files and voice messages need nothing more, and users get back to your app with Done or Back.

swift
import SafariServices

let url = URL(string: "https://chat.sealreply.com/sk_…")!
present(SFSafariViewController(url: url), animated: true)
kotlin
// build.gradle: implementation("androidx.browser:browser:1.8.0")
import androidx.browser.customtabs.CustomTabsIntent

CustomTabsIntent.Builder().build()
    .launchUrl(context, Uri.parse("https://chat.sealreply.com/sk_…"))
dart
// pubspec.yaml: url_launcher: ^6.3.0
import 'package:url_launcher/url_launcher.dart';

await launchUrl(
  Uri.parse('https://chat.sealreply.com/sk_…'),
  mode: LaunchMode.inAppBrowserView,
);
jsx
// npm install react-native-webview
import { WebView } from "react-native-webview";

<WebView source={{ uri: "https://chat.sealreply.com/sk_…" }} />

The chat page has no close button: users leave it with your app's own back or close button.

In a WebView ​

To show the chat inside your app's own screens (in a tab, say), use a WebView (WKWebView on iOS, WebView on Android, react-native-webview). Keep in mind:

  • Turn on JavaScript and local storage. On Android set settings.javaScriptEnabled = true and settings.domStorageEnabled = true. Without them the chat does not open, and users are a new visitor every time.
  • Images and files: an Android WebView needs WebChromeClient.onShowFileChooser, or "+" does nothing. iOS and react-native-webview need nothing.
  • Voice messages: your app needs the microphone permission (NSMicrophoneUsageDescription in Info.plist on iOS; RECORD_AUDIO on Android, granted in WebChromeClient.onPermissionRequest). Or turn voice messages off under Sites → Visitor features.

Apps built from web pages can simply go to the chat page link. The chat page cannot be put in an iframe, so no other website can wrap your chat in its own page.

3. Let agents recognize signed-in users ​

If your app has accounts, you can identify signed-in users as on your website: they show as verified, are the same person on every device, and see their earlier chats in the chat menu.

  1. Turn it on under Sites → your site → Visitor identity verification and keep the secret on your server.

  2. Your app asks your server for the current user's identity_hash: HMAC-SHA256 of the user's ID with the secret, exactly as for your website (how).

  3. Add these after the # of the chat link:

    text
    https://chat.sealreply.com/sk_…#external_id=42&identity_hash=<signature>&name=Ann%20Lee&email=ann%40example.com

    Every value must be URL-encoded. The code below does it for you:

swift
var parts = URLComponents()
parts.queryItems = [
  URLQueryItem(name: "external_id", value: user.id),
  URLQueryItem(name: "identity_hash", value: identityHash), // from your server
  URLQueryItem(name: "name", value: user.name),
  URLQueryItem(name: "email", value: user.email),
]
let url = URL(string: "https://chat.sealreply.com/sk_…#" + (parts.percentEncodedQuery ?? ""))!
present(SFSafariViewController(url: url), animated: true)
kotlin
val fragment = Uri.Builder()
    .appendQueryParameter("external_id", user.id)
    .appendQueryParameter("identity_hash", identityHash) // from your server
    .appendQueryParameter("name", user.name)
    .appendQueryParameter("email", user.email)
    .build().encodedQuery
CustomTabsIntent.Builder().build()
    .launchUrl(context, Uri.parse("https://chat.sealreply.com/sk_…#$fragment"))
dart
final fragment = Uri(queryParameters: {
  'external_id': user.id,
  'identity_hash': identityHash, // from your server
  'name': user.name,
  'email': user.email,
}).query;
await launchUrl(
  Uri.parse('https://chat.sealreply.com/sk_…#$fragment'),
  mode: LaunchMode.inAppBrowserView,
);
jsx
const fragment = Object.entries({
  external_id: user.id,
  identity_hash: identityHash, // from your server
  name: user.name,
  email: user.email,
}).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&");

<WebView source={{ uri: `https://chat.sealreply.com/sk_…#${fragment}` }} />

When a user signs out or another one signs in, open the chat page without these details (or with the new user's). The chat page then starts as a new visitor, and the previous user's chats do not show.

Keep the signature safe

identity_hash works like that user's chat password: anyone who has it can read their chats. Keep the secret on your server and never put it in your app.

A signed link never expires, and whoever has it can chat as that user: build it each time the chat opens, and never log or share it.

What follows # is never sent to any server (ours included), and the chat page removes it from the address as soon as it has read it (a reload in the same tab still knows the user). With Android Custom Tabs, though, Chrome may keep the whole link in that phone's browsing history. If that matters to you, use the WebView way below, which keeps the signature out of the link.

In a WebView, set window.sealreplySettings before the page loads, as on a website, and leave external_id and identity_hash out of the link. If the link has them too, the link wins.

swift
let settings = """
window.sealreplySettings = { identity: {
  external_id: \(jsonString(user.id)),
  identity_hash: \(jsonString(identityHash)),
  name: \(jsonString(user.name))
} };
"""
let config = WKWebViewConfiguration()
config.userContentController.addUserScript(
  WKUserScript(source: settings, injectionTime: .atDocumentStart, forMainFrameOnly: true)
)
let webView = WKWebView(frame: .zero, configuration: config)
webView.load(URLRequest(url: URL(string: "https://chat.sealreply.com/sk_…")!))
kotlin
// build.gradle: implementation("androidx.webkit:webkit:1.12.0")
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewFeature
import org.json.JSONObject

val identity = JSONObject()
    .put("external_id", user.id)
    .put("identity_hash", identityHash)
    .put("name", user.name)
if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
    WebViewCompat.addDocumentStartJavaScript(
        webView,
        "window.sealreplySettings = { identity: $identity };",
        setOf("https://chat.sealreply.com"),
    )
}
webView.loadUrl("https://chat.sealreply.com/sk_…")
jsx
const settings = `window.sealreplySettings = { identity: ${JSON.stringify({
  external_id: user.id,
  identity_hash: identityHash,
  name: user.name,
})} }; true;`;

<WebView
  source={{ uri: "https://chat.sealreply.com/sk_…" }}
  injectedJavaScriptBeforeContentLoaded={settings}
/>

jsonString stands for turning a string into a JSON string literal (quoted and escaped), for example with JSONEncoder. Never paste a user's name into the code as it is.

4. Tell agents where the user came from ​

The widget on your website tells agents the page a visitor is on. An app has no address, so add after the #:

ParameterMeaning
page_titleThe screen's name, such as Order #1024 or Product: Blue jacket. Without it agents see App.
page_urlThe same page on your website (starting with http:// or https://), which agents can open. Leave it out if there is none.
text
https://chat.sealreply.com/sk_…#page_title=Order%20%231024&page_url=https%3A%2F%2Fshop.example.com%2Forders%2F1024

All parameters ​

They all go after the link's #, joined with &, every value URL-encoded. All are optional.

ParameterMeaning
external_idThe user's ID in your system, up to 128 characters
identity_hashThe signature of the user's ID; counts only with external_id
name, emailShown to agents; never replace what the visitor's profile already has
attributes, attributes_hashMember details: attributes is a JSON object (such as {"plan":"gold"}), signed as in Pass member details
page_title, page_urlWhere the user came from, as above

The chat page follows the phone's language, and its dark mode follows the system too (if your site's look is set to follow the visitor's system).

Replies after the user has left your app ​

The chat page receives messages live only while it is open. To reach users who left before an agent replied:

  • Email: when replies are slow, the chat page asks the user for their email (see When you're offline). Signed-in users who passed email are not asked again.
  • Your own push notifications: subscribe to the message.created webhook. For a message from an agent (sender.type is agent), call GET /v1/visitors/{visitor_id} with the event's conversation.visitor_id (the API key needs visitors:read) to get external_id, your user's ID, and notify them with your app's own push. When they tap it, open the chat page again and the reply is there.

Common questions ​

What you seeWhat to do
"Chat is not available right now"Check that Use in your app is on, that the site's domain is verified, that the site is not paused for being over your plan, and that the link's sk_… is complete.
Users are a new visitor every timeThe WebView has no local storage (Android domStorageEnabled), or it is in private mode each time.
Signed-in users are not shown as verifiedThe signature is wrong. Check that you used this site's secret, signed exactly the external_id string, and URL-encoded the values in the link.
"+" does not open the photo pickerAn Android WebView needs onShowFileChooser; or use Custom Tabs.

Chats from your app count toward the site's monthly conversations like those from your website, and sit with your website's visitors in the inbox.