Chat intégré à l’app (WebView)
Intégrez le widget de chat complet dans n’importe quelle application mobile ou de bureau grâce à une WebView : toutes les fonctionnalités, avec mises à jour automatiques.
Pour ajouter du support client dans une application native, le plus rapide est de charger notre page de widget plein écran hébergée dans une WebView. Vous obtenez exactement le même widget que sur le site web (Accueil, Messages, centre d’aide, Recherche, emoji, pièces jointes, horodatage, accusés de lecture, clôture de la conversation), et il se met à jour automatiquement. Une fois l’intégration faite, l’application n’a plus jamais besoin d’une nouvelle version pour suivre les évolutions du widget.
Démarrage rapide
Faites pointer une WebView vers cette URL. Pour un utilisateur connecté, vous devez inclure externalId et email (et idéalement name), sinon l’agent ne verra jamais qu’un visiteur anonyme. Encodez chaque valeur pour l’URL :
https://<your-domain>/api/widget-embed?appId=YOUR_APP_ID&externalId=USER_ID&email=USER_EMAIL&name=USER_NAMECe n’est que si l’utilisateur n’est pas connecté que vous passez à la forme anonyme :
https://<your-domain>/api/widget-embed?appId=YOUR_APP_ID⚠️ L’agent voit un « visiteur anonyme » sans e-mail ?C’est l’erreur d’intégration n° 1 : il manque email/externalId dans l’URL de votre WebView. Le serveur ne peut pas les inventer, c’est à l’application de les transmettre. Si l’identifiant du visiteur ressemble à anon-…, rien n’a été transmis.
JavaScript / toute WebView (hors Flutter)
Avec Electron, un shell de bureau, React Native ou une WKWebView native dont vous définissez vous-même l’URL, construisez l’URL en JS. URLSearchParams encode chaque valeur pour vous :
// Build the support URL for the signed-in user before opening the WebView.
function buildSupportUrl(domain, user) {
const params = new URLSearchParams({
appId: 'YOUR_APP_ID',
platform: 'windows', // Strongly recommended: decides which help articles this visitor sees
// ios / macos_appstore / macos / android / windows / web
// locale: userSelectedLanguage, // Optional: only if your app has its own language switch; also add forceLocale: '1'
// Omitted = follow the device language (covers the App Store's 50 localizations)
// — Always pass these for signed-in users, or agents only see an "anonymous visitor" —
externalId: user.id, // The user's unique ID in your system
email: user.email, // The user's email (important)
name: user.name || '', // The user's name (optional)
// hmac: user.hmac, // Optional: computed on your server to prevent impersonation (see HMAC below)
// attrs: JSON.stringify({ plan: user.plan, expiresAt: user.expireAt }), // Optional: custom attributes
});
return `https://${domain}/api/widget-embed?${params.toString()}`;
}
// Usage: load the returned url in your WebView (instead of the old ?appId=...-only URL).
const url = buildSupportUrl('askais.com', currentUser);
myWebView.loadURL(url); // Electron: win.loadURL(url); native: load this URLFlutter (webview_flutter)
Ajoutez webview_flutter: ^4.x à pubspec.yaml, puis :
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class SupportPage extends StatefulWidget {
const SupportPage({super.key});
@override
State<SupportPage> createState() => _SupportPageState();
}
class _SupportPageState extends State<SupportPage> {
late final WebViewController _controller;
@override
void initState() {
super.initState();
// For signed-in users always include externalId + email, or agents see an "anonymous visitor" (no name / email).
// Pass only appId + locale when the user is not signed in. Uri.https URL-encodes the values.
final uri = Uri.https('askais.com', '/api/widget-embed', {
'appId': 'YOUR_APP_ID',
'platform': Platform.isIOS ? 'ios' : 'android', // Strongly recommended: decides which help articles are visible
// No locale = follow the device language (covers the App Store's 50 localizations).
// Only pass it if your app has its own language switch, together with 'forceLocale': '1':
// 'locale': appSettings.selectedLanguage,
'externalId': user.id, // The user's unique ID in your system (important)
'email': user.email, // The user's email (important)
'name': user.name, // Optional
// 'hmac': hmacFromYourServer, // Optional: computed on your server to prevent impersonation
});
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.white)
..loadRequest(uri);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Support')),
// Full-screen: drop the appBar and use your own close button.
body: SafeArea(child: WebViewWidget(controller: _controller)),
);
}
}
// Open it from wherever your "Support" button lives:
// Navigator.push(context,
// MaterialPageRoute(builder: (_) => const SupportPage()));Pour les WebViews iOS/Android d’autres environnements (Swift WKWebView, Kotlin WebView, React Native react-native-webview), le principe est identique : charger l’URL et activer JavaScript.
iOS et Android natifs
Aucun SDK à installer : vous construisez la même URL et la passez à la WebView. Attention au cas du Mac : un build App Store et un build DMG tournent sur le même système d’exploitation, la valeur doit donc venir d’un flag de compilation, et non d’une vérification à l’exécution.
// ── iOS (Swift / WKWebView) ────────────────────────────────
// On Mac Catalyst / macOS, set platform to macos_appstore or macos.
#if targetEnvironment(macCatalyst)
let platform = "macos_appstore" // App Store build; a DMG build from your website passes "macos"
#else
let platform = "ios" // iPhone and iPad are both ios
#endif
var comps = URLComponents(string: "https://\(domain)/api/widget-embed")!
comps.queryItems = [
.init(name: "appId", value: "YOUR_APP_ID"),
.init(name: "platform", value: platform),
.init(name: "externalId", value: user.id), // Required for signed-in users
.init(name: "email", value: user.email), // Required for signed-in users
.init(name: "name", value: user.name),
// Only pass these two if your app has its own language switch:
// .init(name: "locale", value: settings.language),
// .init(name: "forceLocale", value: "1"),
]
webView.load(URLRequest(url: comps.url!)) // URLComponents URL-encodes the values
// ── Android (Kotlin / WebView) ─────────────────────────────
val url = Uri.parse("https://$domain/api/widget-embed")
.buildUpon()
.appendQueryParameter("appId", "YOUR_APP_ID")
.appendQueryParameter("platform", "android")
.appendQueryParameter("externalId", user.id)
.appendQueryParameter("email", user.email)
.appendQueryParameter("name", user.name)
// .appendQueryParameter("locale", settings.language)
// .appendQueryParameter("forceLocale", "1")
.build()
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true // The widget uses localStorage to remember the visitor
webView.loadUrl(url.toString())Paramètres d’URL
appId(obligatoire) : l’App ID de votre boîte de réception.locale: langue de l’interface. Omettez-le dans presque tous les cas : le widget suit alors la langue de l’appareil indiquée par la WebView, ce qu’attendent les utilisateurs. L’interface est traduite dans les 50 localisations d’App Store Connect ; toute autre langue bascule sur l’anglais. Ne le transmettez que si votre application a son propre sélecteur de langue : envoyez alors la langue choisie par l’utilisateur, avecforceLocale=1pour qu’elle l’emporte sur le réglage de l’appareil. Ne codez jamais de valeur en dur : c’est unzh-Hanscodé en dur qui explique qu’une application japonaise se retrouve avec un widget en chinois.platform(fortement recommandé) : le build utilisé par le visiteur. Il détermine quels articles d’aide celui-ci voit et si l’IA répond selon les règles de l’App Store. Sans lui, la plateforme est devinée à partir du user agent de la WebView : cela reconnaît l’iPhone, Android et Windows, mais ne distingue pas un build Mac App Store d’un build DMG, ni un iPad en mode ordinateur. Valeurs :ios(iPhone et iPad),macos_appstore(build Mac App Store),macos(build DMG distribué directement),android,windows,web.externalId: l’identifiant unique de l’utilisateur dans votre application. Le transmettre permet à l’agent de reconnaître l’utilisateur et de fusionner son historique.email,name,avatarUrl: le profil affiché aux agents.hmac: signature d’identité (voir ci-dessous). Facultatif.
Masquer du contenu selon la plateforme (conformité App Store)
La règle 3.1.1 de l’App Store interdit à une application iOS d’afficher du contenu lié à des achats externes, des abonnements ou du parrainage. Chaque article d’aide et chaque entrée de FAQ dispose donc d’un réglage de visibilité par plateforme : ouvrez la page Centre d’aide ou FAQ de votre tableau de bord et activez les puces de plateforme sur l’élément concerné. Les visiteurs d’une plateforme sélectionnée ne le voient plus dans la liste et ne peuvent pas l’ouvrir par lien direct.
L’IA ne répond pas à partir des articles d’aide ni de la FAQ : elle répond à partir de ce que vous avez ajouté dans « Training ». Les fichiers et les paires questions-réponses qui s’y trouvent ont les mêmes puces de plateforme, et un élément masqué pour une plateforme n’est pas utilisé dans les réponses aux visiteurs de cette plateforme. Masquez-le aux deux endroits si l’IA ne doit pas non plus en parler.
Deux conditions déterminent si cela fonctionne de façon fiable. D’abord, votre application doit envoyer platform : la déduction à partir du user agent n’est qu’un repli. Ensuite, la valeur doit refléter le build, et pas seulement le système d’exploitation :
- L’iPad n’est pas à part. Il exécute la même application iOS, soumise aux mêmes règles, donc
ioscouvre à la fois l’iPhone et l’iPad. - Le Mac a deux builds. Un build Mac App Store est soumis à la règle 3.1.1 ; un DMG distribué depuis votre propre site ne l’est pas. Comme il s’agit du même système d’exploitation, rien ne permet de les distinguer à l’exécution : décidez-le à la compilation (avec un flag Xcode, par exemple) et envoyez
macos_appstoreoumacosen conséquence. En cas d’erreur, soit le build App Store expose du contenu d’abonnement pendant l’examen, soit le build DMG masque la moitié de votre centre d’aide sans raison.
Les modifications s’appliquent immédiatement : le réglage se trouve dans votre tableau de bord, pas dans l’application, vous n’avez donc jamais besoin de publier une nouvelle version pour changer ce qui est visible.
Identité vérifiée (HMAC, facultatif)
Pour prouver qu’un externalId correspond bien à votre utilisateur connecté (et empêcher l’usurpation d’identité), transmettez une signature HMAC. Calculez-la sur votre serveur : ne mettez jamais la clé secrète dans l’application.
hmac = HMAC-SHA256(secretKey, externalId), en hexadécimal minuscule.- La charge utile est l’
externalId, donc transmettez-le toujours : sans lui, le widget utilise son propre identifiant anonyme, et une signature portant sur l’emailest refusée. secretKeyest la clé secrète de votre boîte de réception (une par boîte, à récupérer dans ses paramètres). Sanshmac, le visiteur est considéré comme non vérifié.
import { createHmac } from 'node:crypto';
// SECRET_KEY lives only on your server, never ship it inside the app.
const hmac = createHmac('sha256', SECRET_KEY)
.update(externalId) // exactly the externalId you pass to the widget
.digest('hex');
// Return { externalId, email, hmac } to the app, which appends them to the URL.Notes par plateforme
- Android : conservez l’autorisation
INTERNET; interceptez le bouton Retour pour qu’il revienne d’abord en arrière dans la WebView. Les versions récentes dewebview_flutterprennent en charge<input type="file">pour les pièces jointes. - iOS : HTTPS fonctionne sans exception ATS. L’envoi d’images est une fonction intégrée au widget :
WKWebViewouvre de lui-même le sélecteur de photos natif, vous n’avez rien à développer. Choisir une image dans la photothèque ne demande aucune autorisation. AjoutezNSCameraUsageDescriptionàInfo.plistuniquement si vous voulez proposer l’option appareil photo « Prendre une photo » ; sans cette clé, l’application plante dès qu’un utilisateur la touche.
Vous préférez une interface native ?
Si vous avez besoin d’une interface de chat native développée à la main plutôt que d’une WebView, utilisez les clients « logique seule » de la page SDK mobiles. Pour la plupart des applications, la WebView ci-dessus est plus rapide à livrer et toujours complète.