ebchat 5.1.0
ebchat: ^5.1.0 copied to clipboard
Embeddable ebchat chat SDK for Flutter apps. Drop-in chat screen and widgets on the first-party livechat protocol, with host-owned push ("notification outsourcing"), botflow forms and multi-company support.
ebchat #
Embeddable chat for Flutter apps, on the ebchat first-party livechat protocol. A drop-in chat screen with host-owned push and multi-company support.
v5 is a clean rewrite. It shares no API with the deprecated GetStream-based
ebchat3.x/4.x.
Install #
dependencies:
ebchat: ^5.0.0
Credentials #
You need two values from your ebchat dashboard, under Settings → API keys:
| Value | What it is |
|---|---|
apiKey |
An eb_… token scoped to the LIVECHAT permission |
partnerId |
The partner id the key was issued under |
A LIVECHAT-scoped key can only reach /livechat/v1 — it cannot read your inbox,
your customers, or your integrations. That is what makes it safe to ship inside a
mobile binary. Do not reuse a full-scope dashboard key here.
Quick start #
final chat = await EbChat.init(
baseUrl: Uri.parse('https://api.enable.tech'),
apiKey: 'eb_...', // LIVECHAT-scoped token
partnerId: 'your-app',
user: EbChatUser.guest(), // or EbChatUser.identified(...)
language: 'en', // 'en' | 'ar'
);
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => Scaffold(body: EbChatScreen(chat: chat)),
));
EbChatScreen opens (or resumes) the conversation, renders it with the company's
theme and language, and transparently starts a fresh conversation when an operator
closes the current one. Realtime is used automatically when the gateway advertises
it (the bootstrap realtime block), otherwise it polls.
Call chat.dispose() when you tear the instance down.
Resuming across restarts #
By default the guest session lives in memory, so a restart starts a new
conversation. Pass a SessionStore to persist it — the example app ships a
shared_preferences one (example/lib/prefs_session_store.dart):
final chat = await EbChat.init(
baseUrl: base, apiKey: key, partnerId: partner,
sessionStore: PrefsSessionStore(),
);
Identified users #
When the company enables identity verification, compute the HMAC on your backend — never ship the secret in the app:
// hmac = HMAC_SHA256(userId, companySecret) — from your server
final chat = await EbChat.init(
baseUrl: base, apiKey: key, partnerId: partner,
user: EbChatUser.identified(id: 'crm-123', name: 'Sara', email: '…', hmac: hmac),
);
Identified users get a stable, company-scoped guest id, so the same person keeps one conversation history across devices and reinstalls.
Botflow form questions #
When a flow asks the guest something — a name, a delivery location, a photo of a
prescription — it arrives as a form question and EbChatScreen answers it for
you: text questions focus the composer, and media/location questions upload and
send whatever your picker returns.
Two hooks are all it needs. The SDK bundles no picker plugins, so the camera, file browser and GPS stack stay out of hosts whose flows never ask for them:
EbChatScreen(
chat: chat,
onPickFile: (request) async {
// request.kind is EbChatFormKind.photo or .file
final shot = await ImagePicker().pickImage(source: ImageSource.camera);
if (shot == null) return null; // cancelled — question stays open
return EbChatPickedFile(
bytes: await shot.readAsBytes(),
filename: shot.name,
mimeType: shot.mimeType ?? 'image/jpeg',
);
},
onResolveLocation: () async {
final fix = await Geolocator.getCurrentPosition();
return GeoLocation(latitude: fix.latitude, longitude: fix.longitude);
},
)
example/lib/form_pickers.dart is a complete implementation with permission
handling — copy it. The example's Info.plist and AndroidManifest.xml show the
entries image_picker and geolocator require.
Both hooks are optional. Omit onPickFile and media questions fall back to
your onAttach picker, then to the composer; omit onResolveLocation, or return
null from it when permission is denied, and the guest is asked to type the
address instead. Every question stays answerable by typing, so a missing hook
slows the flow rather than stalling it.
Pass onForm to take over form handling entirely — you then own sending the
answer through the controller.
Push notifications #
ebchat does not bundle Firebase. Pushes are delivered through your app's
Firebase project ("notification outsourcing"): you own firebase_messaging, and
ebchat sends through the service-account credentials you upload to your ebchat
company settings. Nothing arrives until those credentials are in place.
What the gateway sends #
Every bot or operator reply on the guest's conversation produces a notification + data message:
{
"notification": { "title": "Sara", // operator name, or the company for bot replies
"body": "Your order is ready" },
"data": { "type": "ebchat.message.new",
"companyId": "...", "conversationId": "...",
"messageId": "...", "sentAt": "2026-08-10T09:12:44.010Z" }
}
Because the payload carries notification, the OS displays it on its own when
your app is backgrounded or terminated — you do not have to build a local
notification to get the basic "you have a reply" behaviour. The data block is
what you use to route a tap. On Android the messages are tagged per conversation,
so a burst of bot replies collapses into one notification instead of stacking.
If the tenant has previews disabled (health, payments), body is a generic
"New message" and the text stays off the lock screen.
1. Register the device #
Do this after EbChat.init, and again whenever FCM rotates the token:
final token = await FirebaseMessaging.instance.getToken();
if (token != null) {
await chat.registerPushToken(token,
platform: Platform.isIOS ? 'ios' : 'android');
}
FirebaseMessaging.instance.onTokenRefresh.listen((t) =>
chat.registerPushToken(t, platform: Platform.isIOS ? 'ios' : 'android'));
Call chat.unregisterPushToken(token) on logout so a shared device stops
receiving the previous user's replies.
2. Route a tap into the conversation #
EbChatPush.parse returns null for anything that is not an ebchat push, so it is
safe to hand it every message you receive.
// Cold start: the notification that launched the app.
final initial = await FirebaseMessaging.instance.getInitialMessage();
_open(EbChatPush.parse(initial?.data ?? {}));
// Warm start: tapped while the app was in the background.
FirebaseMessaging.onMessageOpenedApp.listen((m) => _open(EbChatPush.parse(m.data)));
void _open(EbChatPush? push) {
if (push == null) return;
// push.companyId picks the EbChat instance; push.conversationId is the thread.
navigatorKey.currentState?.push(MaterialPageRoute(
builder: (_) => Scaffold(body: EbChatScreen(chat: chatFor(push.companyId))),
));
}
3. Foreground behaviour #
While your app is in the foreground the OS does not display the banner by default, and it should not while the user is already looking at the chat.
// iOS: let the banner through when the user is elsewhere in the app.
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
// Android foreground: raise it yourself only when the chat screen isn't open.
FirebaseMessaging.onMessage.listen((m) {
final push = EbChatPush.parse(m.data);
if (push == null || isChatScreenOpen(push.conversationId)) return;
showLocalNotification(m.notification?.title, m.notification?.body);
});
EbChatScreen receives the message over realtime regardless of push, so an open
chat updates whether or not the notification is shown.
4. Platform setup #
- Android — no extra code for the background case; the OS renders it. If you
use a custom notification channel, name it
ebchat_messages(or remap it in your own handler) so importance settings apply. - iOS — enable the Push Notifications capability and Background modes →
Remote notifications, upload your APNs key to the same Firebase project, and
request permission with
FirebaseMessaging.instance.requestPermission(). Without an APNs key, iOS receives nothing.
Multi-company hosts #
Keep one EbChat per API key. A push carries companyId, so
EbChatPush.parse(...) tells you which instance to open.
Local development #
Point baseUrl at your local gateway and use the dev key that
make livechat-up provisions (eb_dev_livechat / partner livechat-dev).
make livechat-info reprints it. On the Android emulator the gateway is
http://10.0.2.2:3000, not localhost.
License #
MIT — see LICENSE.