Skip to main content

Integrate LoyJoy into a Native App

You can embed any LoyJoy agent into a native iOS or Android app with a WebView. The WebView loads the agent's landing page URL and renders the full chat experience inside your app. No SDK, no native chat code, no app store update when you change the agent. You publish the agent in LoyJoy, the app shows the latest version automatically.

This guide covers the basic embed plus the settings you need so that voice, file upload, and chat history work reliably inside the WebView.

When to use a WebView

A WebView is the right choice when you want the agent to run inside your app: as a support tab, an onboarding assistant, a product advisor, or a full-screen chat. Users stay in your app, the agent inherits your branding, and you keep full control over when and where the chat appears.

If you only need to link out to the agent, open the landing page URL in the system browser instead. A WebView is for an in-app, embedded experience.

Step 1: Get the agent URL

Open the Publish tab of your agent and copy the landing page URL. It has the form:

https://cloud.loyjoy.com/sites/YOUR-PROCESS-ID

You can append URL parameters to control the chat, for example ?loyjoy-open=true to open the chat automatically, or pass variables to pre-fill context. See the widget parameters for the full list.

Step 2: iOS (WKWebView)

Use WKWebView (the modern WebView; UIWebView is deprecated and not supported).

import WebKit

let config = WKWebViewConfiguration()
// Required for voice agents and inline audio/video
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []

let webView = WKWebView(frame: view.bounds, configuration: config)
view.addSubview(webView)

if let url = URL(string: "https://cloud.loyjoy.com/sites/YOUR-PROCESS-ID") {
webView.load(URLRequest(url: url))
}

For the microphone (voice agents) and camera (photo snapshot module), add usage descriptions to your Info.plist, otherwise iOS blocks access and the WebView call fails silently:

<key>NSMicrophoneUsageDescription</key>
<string>Allow microphone access to talk to the assistant.</string>
<key>NSCameraUsageDescription</key>
<string>Allow camera access to send photos in the chat.</string>

On iOS 15+, WKWebView grants microphone and camera permission automatically once the Info.plist descriptions are present. localStorage (used for chat history and variables) works out of the box in WKWebView.

Step 3: Android (WebView)

Android requires a few settings that are off by default. Without them, the chat will not start, history will not persist, and file uploads will fail.

val webView = WebView(this)
setContentView(webView)

webView.settings.apply {
javaScriptEnabled = true // required, off by default
domStorageEnabled = true // required for chat history and variables
mediaPlaybackRequiresUserGesture = false // required for voice / inline audio
}

// Grant microphone and camera to the LoyJoy page
webView.webChromeClient = object : WebChromeClient() {
override fun onPermissionRequest(request: PermissionRequest) {
request.grant(request.resources)
}
// onShowFileChooser is also handled here, see Step 4
}

webView.loadUrl("https://cloud.loyjoy.com/sites/YOUR-PROCESS-ID")

Add the runtime permissions your agent needs to AndroidManifest.xml and request them at runtime before loading the WebView:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- voice agents -->
<uses-permission android:name="android.permission.CAMERA" /> <!-- photo module -->

Step 4: Enable file upload (Android)

If your agent uses the photo snapshot or file upload module, Android needs an onShowFileChooser handler in the WebChromeClient. iOS handles this natively, no extra code required.

private var filePathCallback: ValueCallback<Array<Uri>>? = null

override fun onShowFileChooser(
webView: WebView,
callback: ValueCallback<Array<Uri>>,
params: FileChooserParams
): Boolean {
filePathCallback = callback
val intent = params.createIntent()
startActivityForResult(intent, FILE_CHOOSER_REQUEST)
return true
}

Forward the result in onActivityResult to filePathCallback.

Links inside the chat (for example a button that opens a product page) should open in the system browser, not navigate the WebView away from the chat. Intercept navigation and open external URLs externally:

iOS — implement WKNavigationDelegate.decidePolicyFor and open links to other domains with UIApplication.shared.open.

Android — set a WebViewClient and return true from shouldOverrideUrlLoading for external hosts, opening them with an ACTION_VIEW intent.

Keep navigation to cloud.loyjoy.com (and any of your own embedded domains) inside the WebView.

Checklist

CapabilityiOSAndroid
Load chatWKWebView.load(url)loadUrl(url) + javaScriptEnabled = true
Chat history / variablesworks by defaultdomStorageEnabled = true
Voice agent (microphone)NSMicrophoneUsageDescriptionRECORD_AUDIO + onPermissionRequest.grant
Photo / camera moduleNSCameraUsageDescriptionCAMERA + onPermissionRequest.grant
Inline audio / videoallowsInlineMediaPlayback = truemediaPlaybackRequiresUserGesture = false
File uploadnative, no codeonShowFileChooser
External linksdecidePolicyForshouldOverrideUrlLoading

Notes

  • Consent manager: A cookie banner is a website concept. Inside your own native app you typically cover chat-related data handling in your app's privacy policy rather than via a web consent manager. Align the chat history setting (Branding tab) with your app's data policy.
  • Updates without app release: Because the WebView always loads the published agent, you can change the agent in LoyJoy and the app reflects it within ~2 minutes. No app store review needed.
  • Auto-open and parameters: Append ?loyjoy-open=true to open the chat on load. Pass variables via URL parameters to hand context from your app into the chat.

Need help with a specific framework (React Native, Flutter, Capacitor)? Contact support@loyjoy.com.