Mobile SDKs
Put ZebChat inside your own Android, iOS, Flutter or React Native app. Your users chat with your team without leaving the app, and your agents answer them from the same inbox as your website visitors.
What you get
- The full chat in your app. A full-screen chat with every widget feature: your colors and greeting, forms, file and photo uploads, ratings, the AI assistant and your language settings.
- App users in the visitors list. Agents see them live with a phone icon, your app id and version, the device, and the screens they open.
- Your signed-in users. Pass their name and email, or a verified id so their conversations follow them across devices and your website.
- Push notifications for agent replies while the app is in the background, sent through your own Firebase project. The SDKs do not depend on Firebase: your app forwards the token and the message.
- An unread count for a badge on your help button.
| Platform | Package | Minimum |
|---|---|---|
| Android | com.zebchat:chat (Maven Central) | minSdk 23, compileSdk 35 |
| iOS | ZebChat (Swift Package Manager or CocoaPods) | iOS 14, Swift 5.9 |
| Flutter | zebchat_flutter (pub.dev) | Flutter 3.22, Dart 3.4 |
| React Native | @zebchat/react-native (npm) | React Native 0.74 |
Set up in the dashboard
- Open Websites, pick the website whose chat you want in the app, and open the Mobile apps tab. Your site key and install snippets are there too.
- Add your app ids: the Android package name (
applicationId) and the iOS bundle identifier. Apps that are not listed are refused and the chat says it is not available in this app. - For push notifications, upload a Firebase service account JSON for the Firebase project your app uses (Firebase console → Project settings → Service accounts → Generate new private key). Then use Send test push with a device token. Turn off Show message text in notifications if you want pushes without a preview.
Your signed-in users
Every SDK has setUser and logout. Without an id, the name, email and phone are saved as unverified. With identity verification, pass your user id and a hash that your server computes: hex(HMAC-SHA256(identity secret, id)). Never put the identity secret in the app. It works exactly like the website setUser.
import { createHmac } from 'node:crypto';
// The website's identity secret (Websites → your website → Security). Server only.
export function zebchatHash(userId) {
return createHmac('sha256', process.env.ZEBCHAT_IDENTITY_SECRET)
.update(String(userId), 'utf8')
.digest('hex');
}
// Return it with your user's profile, e.g. { id: '42', name: 'Ana', zebchatHash: zebchatHash('42') }Quick starts
Android · iOS · Flutter · React Native
Android (Kotlin or Java)
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
// app/build.gradle.kts
dependencies {
implementation("com.zebchat:chat:1.0.0")
}Call configure in Application.onCreate, then open the chat from anywhere:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
ZebChat.configure(this, siteKey = "zc_…", options = ZebChatOptions(locale = "auto"))
}
}
// Anywhere, e.g. a "Help" button:
ZebChat.show(activity)ZebChat.setUser(ZebChatUser(id = "42", email = "[email protected]", name = "Ana", hash = hashFromYourServer))
ZebChat.logout() // on sign-out: removes this device's push registration, then forgets the visitor
ZebChat.trackScreen("Checkout") // agents see app://<your app id>/Checkout (buffered offline, max 20)
val listener = ZebChat.addUnreadListener { count -> badge.text = count.toString() }
ZebChat.removeUnreadListener(listener)Push: add Firebase Cloud Messaging to your app as usual, then forward tokens and messages from your FirebaseMessagingService (an app can only have one, so the SDK never declares its own):
class MyMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
ZebChat.setPushToken(token)
}
override fun onMessageReceived(message: RemoteMessage) {
if (ZebChat.isZebChatNotification(message.data)) {
// App in the foreground: FCM does not display it, so the SDK does (channel zebchat_chat).
ZebChat.showNotification(this, message.data)
return
}
// … your own messages
}
}
// At start (after configure), as onNewToken only fires on changes:
FirebaseMessaging.getInstance().token.addOnSuccessListener { ZebChat.setPushToken(it) }
// In your launcher Activity: a tap on a notification FCM displayed in the background.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ZebChat.handleNotificationTap(this, intent.extras)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
ZebChat.handleNotificationTap(this, intent.extras)
}iOS (Swift)
// Xcode → File → Add Package Dependencies…
https://github.com/zebchat/zebchat-ios
// or in Package.swift
.package(url: "https://github.com/zebchat/zebchat-ios", from: "1.0.0")pod 'ZebChat', '~> 1.0'Add the camera and photo strings to your app’s Info.plist (see Permissions and privacy), then:
import ZebChat
// AppDelegate.application(_:didFinishLaunchingWithOptions:)
ZebChat.configure(siteKey: "zc_…")
// Open the chat (full screen, modal)
ZebChat.show(from: viewController)// `hash` is computed on YOUR server; never put the identity secret in the app.
ZebChat.setUser(ZebChatUser(id: "42", email: "[email protected]", name: "Ana", hash: hashFromServer))
ZebChat.logout() // sign-out: removes the push registration, then forgets the user and the visitor
ZebChat.trackScreen("Checkout")
let token = ZebChat.addUnreadListener { count in helpButton.badge = count }
ZebChat.removeUnreadListener(token)Push: add FirebaseMessaging to your app, upload your APNs key (.p8) in Firebase → Project settings → Cloud Messaging, and enable the Push Notifications capability in Xcode.
import FirebaseCore
import FirebaseMessaging
import UserNotifications
import ZebChat
// In your AppDelegate (UNUserNotificationCenterDelegate, MessagingDelegate):
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
ZebChat.configure(siteKey: "zc_…")
Messaging.messaging().delegate = self
UNUserNotificationCenter.current().delegate = self
// Ask when it suits your app (the SDK never asks for permission itself).
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { _, _ in }
application.registerForRemoteNotifications()
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
if let fcmToken { ZebChat.setPushToken(fcmToken) }
}
// A push while the app is open: no banner while the chat is visible.
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
if ZebChat.notificationReceived(userInfo) {
completionHandler(ZebChat.isChatVisible ? [] : [.banner, .sound])
} else {
completionHandler([.banner, .sound, .badge])
}
}
// A tapped push opens the chat (also after a cold start).
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
if let root = window?.rootViewController {
ZebChat.handleNotificationTap(response.notification.request.content.userInfo, from: root)
}
completionHandler()
}Flutter
flutter pub add zebchat_flutter
# iOS: in ios/Podfile set platform :ios, '14.0' then
cd ios && pod installAndroid apps need minSdk = 23 or higher. Add the Info.plist strings to ios/Runner/Info.plist.
import 'package:flutter/material.dart';
import 'package:zebchat_flutter/zebchat_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await ZebChat.configure(siteKey: 'zc_your_site_key'); // locale: 'auto' by default
runApp(const MyApp());
}
// Anywhere, for example a "Help" button:
ElevatedButton(onPressed: ZebChat.show, child: const Text('Chat with us'));await ZebChat.setUser(ZebChatUser(
id: user.id,
email: user.email,
name: user.name,
hash: user.zebchatHash, // from your server
));
await ZebChat.logout(); // on sign-out
ZebChat.trackScreen('Checkout'); // or a NavigatorObserver that tracks named routes
StreamBuilder<int>(stream: ZebChat.unreadCount, initialData: 0, builder: …);Push with firebase_messaging (on iOS also enable Background Modes → Remote notifications). Call it after configure:
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:zebchat_flutter/zebchat_flutter.dart';
Future<void> setUpZebChatPush() async {
final messaging = FirebaseMessaging.instance;
await messaging.requestPermission(); // your app decides when to ask
final token = await messaging.getToken();
if (token != null) await ZebChat.setPushToken(token);
messaging.onTokenRefresh.listen(ZebChat.setPushToken);
// Android: FCM does not display notifications while the app is in the foreground.
FirebaseMessaging.onMessage.listen((message) {
if (ZebChat.isZebChatNotification(message.data)) {
ZebChat.showNotification(message.data);
}
});
// Tapped while in the background, or the tap that launched the app.
FirebaseMessaging.onMessageOpenedApp.listen((message) {
ZebChat.handleNotificationTap(message.data);
});
final initial = await messaging.getInitialMessage();
if (initial != null) await ZebChat.handleNotificationTap(initial.data);
}React Native
npm install @zebchat/react-native
# iOS
cd ios && pod installAndroid needs nothing else: com.zebchat:chat comes from Maven Central. Add the Info.plist strings on iOS.
import { useEffect } from 'react';
import { Button } from 'react-native';
import { ZebChat, useZebChatUnread } from '@zebchat/react-native';
export function App() {
useEffect(() => {
ZebChat.configure({ siteKey: 'zc_your_site_key' });
}, []);
const unread = useZebChatUnread();
return <Button title={`Chat with us (${unread})`} onPress={() => ZebChat.show()} />;
}// `hash` is the HMAC your server computes for identity verification.
ZebChat.setUser({ id: user.id, email: user.email, name: user.name, hash: user.zebchatHash });
ZebChat.logout(); // removes this device's push token, then forgets the user and the visitor
ZebChat.trackScreen('Checkout');Push with @react-native-firebase/messaging:
import { PermissionsAndroid, Platform } from 'react-native';
import messaging from '@react-native-firebase/messaging';
import { ZebChat } from '@zebchat/react-native';
export async function setUpZebChatPush() {
if (Platform.OS === 'android' && Platform.Version >= 33) {
await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
} else {
await messaging().requestPermission();
}
ZebChat.setPushToken(await messaging().getToken());
messaging().onTokenRefresh((token) => ZebChat.setPushToken(token));
// App in the foreground: Android shows it on the zebchat_chat channel.
messaging().onMessage(async (message) => {
if (ZebChat.isZebChatNotification(message.data)) ZebChat.showNotification(message.data);
});
// Tapped while in the background, or the tap that launched the app.
messaging().onNotificationOpenedApp((message) => ZebChat.handleNotificationTap(message.data));
const initial = await messaging().getInitialNotification();
if (initial) ZebChat.handleNotificationTap(initial.data);
}How push notifications work
- ZebChat sends a push when an agent (or the AI assistant) replies while the user is not in the chat: the app is in the background or closed. The chat disconnects as soon as it is hidden, so the user counts as offline right away.
- Pushes go through your Firebase project (FCM for Android and iOS; Firebase forwards to APNs). They carry only
{zebchat, conversationId, siteKey, title, body}, no visitor data. On Android they use the channelzebchat_chat; on iOS the badge is the unread count. - A user who also has your website open in a browser tab counts as online and gets no push while that tab is open.
Permissions and privacy
- Android 13+ notifications: the SDK never asks for
POST_NOTIFICATIONS. Declare and request it yourself, when it suits your app. The SDK declares onlyINTERNET; if your app declares and holdsCAMERA, the file picker also offers “take a photo”. - iOS Info.plist (required): the photo picker opens from the chat, so your app must explain camera and photo access, or iOS closes the app when the picker opens.
<key>NSCameraUsageDescription</key>
<string>Take a photo to send in the chat.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Choose photos to send in the chat.</string>- Data the SDKs send to ZebChat: what you pass to
setUser(name, email, phone, user id); messages, photos and files the user sends; screen names fromtrackScreen; a random visitor key and the push token; app id and version, SDK version, OS version, device model and type. No advertising ID, no tracking, nothing shared beyond your own Firebase project for pushes. - The visitor key stays in the Keychain (iOS) or private storage excluded from backups (Android) and never enters the chat page or a URL. The iOS SDK ships a privacy manifest; each README lists the answers for the App Store and Google Play data forms.
FAQ
Does it work with Expo?
Yes, in a development build after npx expo prebuild. No config plugin is needed. Expo Go is not supported because it cannot load native modules.
What happens offline?
The chat shows that it is offline and reconnects on its own when the network comes back. Screen views are buffered (up to 20) and sent later, and sessions are cached so a cold start rarely waits for the network.
Which plans include the mobile SDKs?
All of them, push notifications included. Identity verification (the verified id and hash) is a plan feature, as on the website: see Pricing.
Does the app chat use my widget settings?
Yes. The SDKs show ZebChat’s own full-screen chat with the website’s widget settings, so changes and new widget features reach your app without an app update.