Jetlink Mobile WebView user guide
Open the Jetlink chat interface inside a WebView in your iOS and Android applications, pass information about logged-in users, and enable secure user authentication with JWT.
This guide is intended for mobile and backend developers. It covers setup, platform-specific App ID and App Key values, WebView URL parameters, and two methods for identifying users after login. The mobile integration opens Jetlink's ready-to-use WebView URL; the examples in this guide use that URL.
Recommended method: JWT user authentication. We recommend using a server-signed JWT to pass logged-in user identities to Jetlink. This is more secure than direct URL fields and protects against modified user IDs or profile data being accepted as a valid identity. Choose JWT for new integrations and plan to migrate existing integrations to JWT.
1. How the mobile integration works
- Add a chat entry point to your application. Use an icon or button in the menu, header bar, or another easily accessible location.
- Prepare the platform-specific WebView URL. Use the App ID and App Key from the relevant iOS or Android dashboard screen.
- Build the URL for the user's login state. Use the base URL for anonymous visitors. For logged-in users, use direct user fields or the recommended JWT method.
- When the icon is tapped, open the URL in an in-app WebView. The Jetlink chat interface loads and the user can start a conversation.
Load the prepared HTTPS URL using WKWebView on iOS or your application's WebView component on Android. The backend examples generate the complete URL; the Swift and Kotlin examples in section 5 show how to open it in a WebView when the assistant icon is tapped.
2. Getting platform credentials from the dashboard
In the Jetlink dashboard, use the Settings menu and open the relevant mobile platform screen. Get each platform's App ID and App Key from its own screen.
| Platform | Dashboard screen | mobileSDKType in the URL |
|---|---|---|
| iOS | Settings → iOS | ios |
| Android | Settings → Android | android |
Web View tab
On the iOS screen, the Web View tab contains Bundle ID, Application ID, App Token, Web View Link and Web View After Login Link fields. For Android, get the corresponding platform credentials and WebView links from the Android screen.
| Dashboard field | Usage |
|---|---|
| Bundle ID (iOS) | Identifies the iOS application's bundle. Do not use this value instead of appId in the WebView URL. |
| Application ID | Maps to the appId parameter in the WebView URL. |
| App Token | Maps to the appKey parameter in the WebView URL. |
| Web View Link | The base WebView URL without user information. Use the copy icon next to it to copy the link. |
| Web View After Login Link | The URL template for passing user information as direct query parameters. The JWT method is explained separately below. |
| JWT secret | Available in the User Authentication (JWT) tab. Used only in the backend to sign tokens. |
Do not mix platform credentials. iOS and Android have different App ID and App Key values. Setting onlymobileSDKType=androidis not enough for Android; you must also use the App ID and App Key from the Android screen. The dashboard App Token is not the JWT secret.
3. WebView URL structure and anonymous usage
Both platforms use the base URL https://public.jetlink.io/Home/MobilSDK. Use the MobilSDK path and parameter names exactly as shown in the examples.
iOS base URL
https://public.jetlink.io/Home/MobilSDK?appId=YOUR_IOS_APP_ID&appKey=YOUR_IOS_APP_KEY&mobileSDKType=ios&mobileDeviceName=DEVICE_NAME&mobileDeviceOS=DEVICE_OS_VERSION
Android base URL
https://public.jetlink.io/Home/MobilSDK?appId=YOUR_ANDROID_APP_ID&appKey=YOUR_ANDROID_APP_KEY&mobileSDKType=android&mobileDeviceName=DEVICE_NAME&mobileDeviceOS=DEVICE_OS_VERSION
| Parameter | Description |
|---|---|
appId | The Application ID for the relevant mobile platform. |
appKey | The App Token for the relevant mobile platform. |
mobileSDKType | Use ios for iOS and android for Android. |
mobileDeviceName | Name/model of the device running the application. |
mobileDeviceOS | The device's operating system version. |
Values such as {{device_name}} and {{device_os}} in the dashboard are placeholders. Replace them with actual device information before opening the URL. The App ID and App Key values in the examples are also placeholders.
URL-encode query parameter values. Do not concatenate raw values containing spaces, +, &, or non-ASCII characters. In particular, the + in a phone number may be interpreted as a space if not encoded correctly. Use URL-building libraries and avoid encoding the same value twice.
The base URL without user information or a JWT is for anonymous usage. Visitors without a JWT can use Messenger anonymously even when JWT authentication is enabled.
4. Passing user information after login
After a user logs in to your application, you can identify them to Jetlink in two ways. In both methods, use the same persistent user ID consistently.
| Topic | Direct user data | JWT — Recommended |
|---|---|---|
| Data added to the URL | username, userSurname, userEmail, userPhone, userSourceUserId | userJwt |
| Identity assurance | User fields can be changed on the client; this alone does not provide server-signed identity verification. | Uses server-signed user information; modified identity data is not accepted without a valid signature. |
| Dashboard setting | JWT authentication disabled. | JWT authentication enabled. |
| Recommendation | Documented for compatibility with existing integrations. | Recommended for new integrations and for upgrading existing systems to secure user identification. |
4.1. Method 1 — Passing user information directly
When JWT authentication is disabled, you can add user information to the WebView URL as separate parameters. The dashboard's Web View After Login Link field provides the template for this method.
https://public.jetlink.io/Home/MobilSDK?appId=YOUR_IOS_APP_ID&appKey=YOUR_IOS_APP_KEY&mobileSDKType=ios&mobileDeviceName=DEVICE_NAME&mobileDeviceOS=DEVICE_OS_VERSION&username=John&userSurname=Doe&userEmail=john.doe%40example.com&userPhone=%2B905551112233&userSourceUserId=100001
Use the same user parameters on Android; replace App ID and App Key with the Android channel values, and set mobileSDKType to android.
| Parameter | Description | JWT equivalent |
|---|---|---|
username | The user's first name, not a username or login name. | name |
userSurname | Last name. | surname |
userEmail | Email address. | email |
userPhone | Phone number. | phone |
userSourceUserId | The unique, persistent user ID in your system. | user_id |
The README allows you to send all or some of the information available in your system. The Jetlink interface opens with these details and supports recognizing the user in subsequent conversations through that information. Keep the persistent user ID consistent for reliable user mapping.
Migrate to JWT for secure identification. Having a name, phone number, or user ID in the URL does not prove that the server has verified it. We recommend the JWT method below to identify logged-in users reliably.
4.2. Method 2 — Secure identification with JWT (recommended)
With JWT, user information is prepared in the backend, signed with HS256 using the relevant channel's JWT secret, and added to the WebView URL as the userJwt parameter. When JWT is enabled, users are identified only through a valid token, and user data is taken only from that token.
Accessing JWT settings and the secret
- For the platform you are integrating, open Settings → iOS or Settings → Android.
- Open the User Authentication (JWT) tab.
- Enable Authenticate users with JWT.
- Select a duration in the JWT validity period field.
- Copy the key using the copy icon next to the JWT secret field and store the key in your backend's secret storage.
Available periods: 5 minutes, 10 minutes, 15 minutes, 30 minutes, 1 hour, 12 hours, 1 day, 7 days, and 30 days. Tokens with iat values older than the selected period are rejected.
Use the key shown on the relevant JWT screen for each platform. Do not assume the platforms share the same JWT secret. The examples use separate backend variables, JETLINK_IOS_JWT_SECRET and JETLINK_ANDROID_JWT_SECRET; these are example variable names in your server configuration.
Do not put the JWT secret in the mobile application. The secret must not be included in the iOS/Android app package, JavaScript inside the WebView, or the URL. The mobile application receives only the server-signed token or the complete WebView URL containing it.
JWT payload fields
user_id and iat are required; all other fields are optional.
| Field | Type | Description |
|---|---|---|
user_id | String · Required | The user's unique, persistent ID in your own system. |
iat | Number · Required | Token creation time as a Unix timestamp in seconds. |
email | String · Optional | Email address. |
phone | String · Optional | Phone number. |
name | String · Optional | First name. |
surname | String · Optional | Last name. |
avatar_url | String · Optional | Profile image URL. |
custom_fields | Object · Optional | Additional user information, such as role and department. |
{ "user_id": "100001", "email": "john.doe@example.com", "phone": "905551112233", "name": "John", "surname": "Doe", "avatar_url": "https://example.com/avatar.png", "custom_fields": { "role": "customer", "department": "sales" }, "iat": 1790860807 }
The example iat is illustrative only. Use the current server time in production; do not copy the fixed timestamp. Values such as role in custom_fields do not replace your application's authorization checks.
Backend flow after login
- The backend verifies the user's existing application session.
- User information is read from a trusted server record; an arbitrary user ID supplied by the client is not signed.
- The App ID, App Key, and JWT secret for the target platform are selected from the backend configuration.
- The backend generates the token and adds it to the WebView URL as
userJwt. - The complete WebView URL is passed to the mobile app through the existing login/session response.
- When the user opens the chat, the mobile app loads this URL in a WebView.
The following examples are helper functions called within your existing backend flow. Do not move token generation into JavaScript inside the WebView. No separate API call is made from the WebView page to obtain a token; the examples assume that the mobile app receives a complete URL through the existing authenticated session flow.
Node.js — Generating the token and WebView URL in the backend
npm install jsonwebtoken
const jwt = require("jsonwebtoken"); function createJetlinkMobileUrl(user, platform, device) { // user: the user record verified by the backend. if (!user || user.id == null || String(user.id).trim() === "") { throw new Error("A verified user ID is required."); } if (platform !== "ios" && platform !== "android") { throw new Error("Unsupported mobile platform."); } const prefix = platform === "ios" ? "JETLINK_IOS" : "JETLINK_ANDROID"; const appId = process.env[prefix + "_APP_ID"]; const appKey = process.env[prefix + "_APP_KEY"]; const secret = process.env[prefix + "_JWT_SECRET"]; if (!appId || !appKey || !secret) { throw new Error("Jetlink configuration is missing for the selected platform."); } const payload = { user_id: String(user.id), email: user.email, phone: user.phone, name: user.firstName, surname: user.lastName, avatar_url: user.avatarUrl, custom_fields: { role: user.role, department: user.department } }; // jsonwebtoken automatically adds the current iat in seconds. const userJwt = jwt.sign(payload, secret, { algorithm: "HS256" }); const url = new URL("https://public.jetlink.io/Home/MobilSDK"); url.searchParams.set("appId", appId); url.searchParams.set("appKey", appKey); url.searchParams.set("mobileSDKType", platform); url.searchParams.set("mobileDeviceName", device.name); url.searchParams.set("mobileDeviceOS", device.osVersion); url.searchParams.set("userJwt", userJwt); return url.toString(); } // Within your existing backend login/session flow: // const webViewUrl = createJetlinkMobileUrl(verifiedUser, "ios", device); // Pass the complete URL to the mobile app in your session response. // For Android, use platform = "android".
verifiedUser and device represent data objects in your existing application. Device name/version is not proof of identity. The URL builder encodes values; do not pre-encode parameters, including userJwt.
C# / ASP.NET Core — Generating the token and WebView URL in the backend
dotnet add package System.IdentityModel.Tokens.Jwt
The following model and helper class generate the user JWT. Populate the user object from the backend-verified session and user record.
using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Text; using Microsoft.IdentityModel.Tokens; public sealed record JetlinkUser( string Id, string? Email = null, string? Phone = null, string? Name = null, string? Surname = null, string? AvatarUrl = null, Dictionary<string, object>? CustomFields = null); public static class JetlinkTokenFactory { public static string Create(JetlinkUser user, string secret) { if (string.IsNullOrWhiteSpace(user.Id)) throw new ArgumentException("A user ID is required."); if (string.IsNullOrWhiteSpace(secret)) throw new ArgumentException("The JWT secret is required."); // Use the text value of the key from the dashboard. var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); var credentials = new SigningCredentials( signingKey, SecurityAlgorithms.HmacSha256); var payload = new JwtPayload { ["user_id"] = user.Id, ["iat"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds() }; // Omit empty optional fields. if (!string.IsNullOrWhiteSpace(user.Email)) payload["email"] = user.Email; if (!string.IsNullOrWhiteSpace(user.Phone)) payload["phone"] = user.Phone; if (!string.IsNullOrWhiteSpace(user.Name)) payload["name"] = user.Name; if (!string.IsNullOrWhiteSpace(user.Surname)) payload["surname"] = user.Surname; if (!string.IsNullOrWhiteSpace(user.AvatarUrl)) payload["avatar_url"] = user.AvatarUrl; if (user.CustomFields is { Count: > 0 }) payload["custom_fields"] = user.CustomFields; var token = new JwtSecurityToken(new JwtHeader(credentials), payload); return new JwtSecurityTokenHandler().WriteToken(token); } }
Use the following helper class to add the generated token to the relevant platform URL. QueryHelpers is a URL-building helper for ASP.NET Core applications.
using System; using System.Collections.Generic; using Microsoft.AspNetCore.WebUtilities; public static class JetlinkMobileUrlFactory { public static string Create( JetlinkUser verifiedUser, string platform, string deviceName, string deviceOsVersion) { var prefix = platform switch { "ios" => "JETLINK_IOS", "android" => "JETLINK_ANDROID", _ => throw new ArgumentException("Unsupported mobile platform.") }; string RequiredSetting(string suffix) { var value = Environment.GetEnvironmentVariable(prefix + suffix); return !string.IsNullOrWhiteSpace(value) ? value : throw new InvalidOperationException(prefix + suffix + " must be configured."); } var userJwt = JetlinkTokenFactory.Create( verifiedUser, RequiredSetting("_JWT_SECRET")); return QueryHelpers.AddQueryString( "https://public.jetlink.io/Home/MobilSDK", new Dictionary<string, string?> { ["appId"] = RequiredSetting("_APP_ID"), ["appKey"] = RequiredSetting("_APP_KEY"), ["mobileSDKType"] = platform, ["mobileDeviceName"] = deviceName, ["mobileDeviceOS"] = deviceOsVersion, ["userJwt"] = userJwt }); } } // Within your existing backend login/session flow: // var webViewUrl = JetlinkMobileUrlFactory.Create( // verifiedUser, "ios", deviceName, deviceOsVersion); // Pass the complete URL to the mobile app in your session response.
For both backend examples, configure JETLINK_IOS_APP_ID, JETLINK_IOS_APP_KEY, JETLINK_IOS_JWT_SECRET and their JETLINK_ANDROID_... equivalents using the relevant dashboard values. Do not interchange the App Key and JWT secret. Use the secret as copied text from the dashboard; do not apply Base64/hex decoding to it.
Resulting WebView URLs with JWT
SERVER_GENERATED_USER_JWT represents the backend-generated token. The mobile application only opens the complete URL in a WebView.
iOS: https://public.jetlink.io/Home/MobilSDK?appId=YOUR_IOS_APP_ID&appKey=YOUR_IOS_APP_KEY&mobileSDKType=ios&mobileDeviceName=DEVICE_NAME&mobileDeviceOS=DEVICE_OS_VERSION&userJwt=SERVER_GENERATED_USER_JWT Android: https://public.jetlink.io/Home/MobilSDK?appId=YOUR_ANDROID_APP_ID&appKey=YOUR_ANDROID_APP_KEY&mobileSDKType=android&mobileDeviceName=DEVICE_NAME&mobileDeviceOS=DEVICE_OS_VERSION&userJwt=SERVER_GENERATED_USER_JWT
In the mobile URL, the parameter name must be exactly userJwt. Put profile fields in the token payload; when JWT is enabled, do not attempt to change user data by also sending URL fields such as username, userEmail.
A JWT signature is not encryption. Because the token is carried in the URL, it is visible to the mobile application and WebView, and its payload can be read. Security depends on keeping the signing key exclusively on the server. Do not record the complete URL or token in logs, analytics, or error reports; do not include passwords or other services' access tokens in the payload.
4.3. Migrating an existing integration to JWT
- Check the iOS and Android App ID, App Key, and JWT settings on their respective screens.
- Consistently map the existing
userSourceUserIdvalue to the JWTuser_idfield. - Implement token and platform-specific WebView URL generation in the backend.
- Have the mobile application receive the complete URL through the existing session flow and open it in a WebView.
- Replace direct profile parameters with
userJwt. - Coordinate enabling JWT authentication with backend and mobile app releases. If an older app version does not send a token, it will continue anonymously after JWT is enabled.
- Test authenticated users, anonymous users, and account switching on both platforms.
Users are shared across channels. For full protection, enable JWT on all relevant channels, including Web Messenger, iOS, and Android.
5. Opening a WebView from the assistant icon
The mobile application's role is to open the fully prepared Jetlink URL in a WebView screen. The user taps the assistant icon, the app opens the chat screen, and the complete URL is loaded. JWT generation and URL preparation are completed in the backend as described in the previous section.
| Component | Responsibility |
|---|---|
| Backend | Verify the user, generate a JWT if needed, and prepare the complete platform-specific WebView URL. |
| Mobile application | Receive the complete URL through the existing session flow, display the assistant icon, and open the WebView screen when tapped. |
| Jetlink WebView page | Load the chat interface using the channel and user information in the URL. |
The examples below assume that jetlinkWebViewUrl is a complete, usable URL for the relevant platform, received through the application's existing backend flow. They do not make a new token API request or generate tokens on the client. The same WebView opening code supports anonymous URLs, URLs with direct user fields, and JWT URLs.
5.1. iOS — Swift and WKWebView
This UIKit example adds an assistant icon button to the application screen. Tapping it opens a separate WKWebView screen that loads the complete URL. The Close button returns the user to the application screen. The system icon used here requires iOS 13 or later.
Application screen with an assistant icon
import UIKit final class HomeViewController: UIViewController { // The complete WebView URL from your existing backend/session flow. var jetlinkWebViewUrl: String? override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground let assistantButton = UIButton(type: .system) assistantButton.setImage( UIImage(systemName: "message.fill"), for: .normal) assistantButton.accessibilityLabel = "Open assistant" assistantButton.translatesAutoresizingMaskIntoConstraints = false assistantButton.addTarget( self, action: #selector(openAssistant), for: .touchUpInside) view.addSubview(assistantButton) NSLayoutConstraint.activate([ assistantButton.widthAnchor.constraint(equalToConstant: 56), assistantButton.heightAnchor.constraint(equalToConstant: 56), assistantButton.trailingAnchor.constraint( equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), assistantButton.bottomAnchor.constraint( equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16) ]) } @objc private func openAssistant() { guard let value = jetlinkWebViewUrl, let url = URL(string: value), url.scheme == "https", url.host == "public.jetlink.io", url.path == "/Home/MobilSDK" else { let alert = UIAlertController( title: "Unable to open the assistant", message: "The chat link is not ready yet.", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) present(alert, animated: true) return } let chat = JetlinkChatViewController(webViewURL: url) let navigation = UINavigationController(rootViewController: chat) navigation.modalPresentationStyle = .fullScreen present(navigation, animated: true) } }
Chat screen that loads the complete URL
import UIKit import WebKit final class JetlinkChatViewController: UIViewController, WKNavigationDelegate { private let webViewURL: URL private var webView: WKWebView! init(webViewURL: URL) { self.webViewURL = webViewURL super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("Create this screen using init(webViewURL:).") } override func viewDidLoad() { super.viewDidLoad() title = "Assistant" view.backgroundColor = .systemBackground navigationItem.leftBarButtonItem = UIBarButtonItem( title: "Close", style: .plain, target: self, action: #selector(closeChat)) let configuration = WKWebViewConfiguration() if #available(iOS 14.0, *) { configuration.defaultWebpagePreferences.allowsContentJavaScript = true } webView = WKWebView(frame: .zero, configuration: configuration) webView.navigationDelegate = self webView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(webView) NSLayoutConstraint.activate([ webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), webView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), webView.trailingAnchor.constraint(equalTo: view.trailingAnchor) ]) // The URL is complete; no token or query parameters are generated here. webView.load(URLRequest(url: webViewURL)) } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { // Show an error without logging the URL or token. guard (error as NSError).code != NSURLErrorCancelled else { return } let alert = UIAlertController( title: "Unable to connect", message: "Check your Internet connection and try again.", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) present(alert, animated: true) } @objc private func closeChat() { webView.stopLoading() dismiss(animated: true) } }
HomeViewController.jetlinkWebViewUrl should be populated with the iOS URL from your existing session flow. Perform this assignment and UI operations on the main thread. Do not add App ID, App Key, or a token again; load the prepared URL as received. URL validation here only checks the format/address, not the token's signature or validity period.
5.2. Android — Kotlin and WebView
This example is for a Kotlin application using AndroidX Activity. The assistant icon on the main screen passes the complete URL to a separate Activity, which opens it using WebView.loadUrl.
AndroidManifest.xml settings
Add the Internet permission under manifest and the chat Activity inside the existing application element. Keep your existing main Activity declaration; the following snippet shows the entries to merge.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> <application> <!-- Keep your existing application and main Activity settings here. --> <activity android:name=".JetlinkChatActivity" android:exported="false" /> </application> </manifest>
Application screen with an assistant icon
import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.Gravity import android.widget.FrameLayout import android.widget.ImageButton import android.widget.Toast import androidx.activity.ComponentActivity class MainActivity : ComponentActivity() { // The complete Android WebView URL from your existing backend/session flow. var jetlinkWebViewUrl: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val root = FrameLayout(this) setContentView(root) val assistantButton = ImageButton(this).apply { setImageResource(android.R.drawable.ic_menu_help) contentDescription = "Open assistant" setOnClickListener { openAssistant() } } val density = resources.displayMetrics.density val size = (56 * density).toInt() val margin = (16 * density).toInt() root.addView(assistantButton, FrameLayout.LayoutParams( size, size, Gravity.BOTTOM or Gravity.END ).apply { setMargins(margin, margin, margin, margin) }) } private fun openAssistant() { val value = jetlinkWebViewUrl val uri = value?.let { Uri.parse(it) } if (uri == null || uri.scheme != "https" || uri.host != "public.jetlink.io" || uri.path != "/Home/MobilSDK") { Toast.makeText(this, "The chat link is not ready yet.", Toast.LENGTH_SHORT).show() return } startActivity(Intent(this, JetlinkChatActivity::class.java).apply { putExtra(JetlinkChatActivity.EXTRA_URL, value) }) } }
Chat Activity that loads the complete URL
import android.annotation.SuppressLint import android.net.Uri import android.os.Bundle import android.webkit.WebView import android.webkit.WebViewClient import android.widget.Button import android.widget.LinearLayout import androidx.activity.ComponentActivity import androidx.activity.OnBackPressedCallback class JetlinkChatActivity : ComponentActivity() { companion object { const val EXTRA_URL = "jetlink_webview_url" } private var webView: WebView? = null @SuppressLint("SetJavaScriptEnabled") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val value = intent.getStringExtra(EXTRA_URL) val uri = value?.let { Uri.parse(it) } if (uri == null || uri.scheme != "https" || uri.host != "public.jetlink.io" || uri.path != "/Home/MobilSDK") { finish() return } val root = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL } val closeButton = Button(this).apply { text = "Close" setOnClickListener { finish() } } val chat = WebView(this).apply { settings.javaScriptEnabled = true settings.domStorageEnabled = true webViewClient = WebViewClient() } webView = chat root.addView(closeButton, LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)) root.addView(chat, LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f)) setContentView(root) onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { if (chat.canGoBack()) chat.goBack() else finish() } }) // The URL is complete; no token or query parameters are generated here. chat.loadUrl(uri.toString()) } override fun onDestroy() { webView?.let { chat -> chat.stopLoading() (chat.parent as? android.view.ViewGroup)?.removeView(chat) chat.destroy() } webView = null super.onDestroy() } }
Place the classes in your application's package structure; the Activity name in the manifest must refer to the same class. Assign the Android URL received from the backend to MainActivity.jetlinkWebViewUrl. If you already have a main screen, add the icon and click handler to it instead of replacing the entire screen.
The example enables JavaScript and DOM storage; WebViewClient keeps normal page navigation inside the WebView. Android's system Back action first navigates through WebView history; if there is no history, it closes the chat screen. The Close button returns directly to the application screen. Apply your app's existing edge-to-edge and system bar inset handling to this screen as well.
Scope of the examples
These examples cover tapping the assistant icon, opening the complete URL in a WebView, and leaving the chat screen. If your integration uses file selection, camera/microphone access, downloads, new windows, or external links, handle these separately through your application's WebView delegates/clients and permission flows. Do not assume that the basic opening code supports all of these features.
Closing the WebView or calling destroy() on Android should not be treated as clearing the user session. For logout and account switching, follow the checks in section 6. Compile the native examples in your own iOS/Android project and test them on a physical device.
6. Token validity and user sessions
When should you generate the token?
Generate a new token on each page load or application session. If the user opens the chat long after login, the token in the URL prepared at the start of the session may be too old. Ensure that the token in the URL loaded by the WebView is within the selected validity period; if needed, prepare a URL with a fresh token through your existing backend session flow.
iat is the creation time and must be in seconds. It is added automatically in the Node.js example; the C# example uses DateTimeOffset.UtcNow.ToUnixTimeSeconds(). Keep the server clock accurate. The documented validity check uses iat and the dashboard period; adding exp alone does not replace this check.
Logout and account switching
When a user logs out, do not reuse the old token or the old token-containing WebView URL for the next user. Generate a new token in the backend for the new user. Closing the WebView alone does not confirm that cookies, local storage, or the Messenger session have been cleared; verify session cleanup and reopening behavior in your mobile integration.
Test that user A's identity and conversations do not appear after logging out as A and logging in as B. Token expiry does not imply that an open chat automatically closes or that the SDK automatically renews the token.
Key rotation
When changing the key using the refresh icon on the JWT screen, update the relevant backend configuration in a controlled rollout. Do not assume that tokens signed with the old key will continue to work; verify the integration with WebView URLs containing newly generated tokens.
7. Testing and troubleshooting
Test iOS and Android separately using their own channel credentials. Seeing the chat screen does not by itself prove that the correct user has been identified; also check the user information associated with the conversation in Jetlink.
| Test / symptom | Check or expected result |
|---|---|
| Tapping the assistant icon | Chat should open in an in-app WebView, loading the prepared URL for the correct platform. |
| Close and Back behavior | Verify that iOS Close and Android Close/Back return to the application screen as expected. |
| Anonymous base URL | Messenger should open anonymously. Anonymous usage remains available without a token when JWT is enabled. |
| JWT disabled, direct user fields | Check the submitted user information and persistent user ID. |
| JWT enabled, valid token | The user is identified by the token's user_id; profile fields belong to the correct user. |
| Wrong key or modified payload | The token must not be accepted as a verified user identity. |
| Missing user_id / iat or an old iat | Must not provide valid user authentication; repeat with a complete, fresh token. |
| WebView does not open or the wrong channel appears | Check that App ID/App Key come from the same correct platform screen, the mobileSDKType value, HTTPS connectivity, and WebView loading errors. |
| The user appears anonymous | Check the userJwt parameter name, confirm that a token is included in the URL, and check the signature, token age, and platform JWT setting. |
| Name or phone is passed incorrectly | Check parameter encoding, especially spaces, +, &, and non-ASCII characters. |
| Different direct user data sent alongside JWT | Verify that data is taken only from the token when JWT is enabled. |
| The previous user appears after account switching | Check for reuse of an old URL/token, WebView storage, and session cleanup. |
| Works on one platform but not the other | Check each platform's separate App ID, App Key, JWT secret, authentication setting, and mobileSDKType mapping. |
For support, provide the platform, application/OS version, relevant channel, time of the issue, and redacted error details. Do not include the JWT secret or the complete token-containing WebView URL in support records.
Frequently asked questions
Can I switch from iOS to Android by changing only mobileSDKType?
No. Use the App ID and App Key from the Android screen, and set mobileSDKType to android. If you use JWT, also use the Android channel's JWT settings.
Are App Token and userJwt the same thing?
No. The dashboard App Token is the URL's appKey parameter. userJwt is the token generated by the backend by signing user information, specific to the user and its creation time. The JWT secret is a separate server-side secret.
Can visitors who are not logged in chat when JWT is enabled?
Yes. Visitors without a JWT can use Messenger anonymously.
Can the token be generated in the mobile application?
Sign tokens only in the backend. Do not distribute the JWT secret to the mobile app; the app should open the URL containing the server-generated token.
Does JWT encrypt user information?
No. A JWT signature supports data integrity and authentication; it does not conceal the payload. Use HTTPS for transport and treat the token as sensitive.
Why is JWT recommended instead of direct user data?
Direct URL fields can be changed on the client. With JWT, user information is signed by the backend, so a client without the secret cannot generate a valid signature for modified data. This is why JWT is the recommended method for identifying users after login.
Sources: The supplied Jetlink iOS WebView README, the iOS Web View and User Authentication (JWT) screens, and product descriptions of Android platform differences. Official website: jetlink.io. Native WebView references: Apple WKWebView and Android WebView.