zendesk_messaging 3.2.3
zendesk_messaging: ^3.2.3 copied to clipboard
Flutter plugin for Zendesk Messaging SDK. Provides in-app customer support messaging with multi-conversation support, real-time events, and JWT authentication.
Zendesk Messaging for Flutter #

English | 繁體中文 | 简体中文 | Español | Português (Brasil) | 日本語 | 한국어
A Flutter plugin for integrating Zendesk Messaging SDK into your mobile applications. Provides in-app customer support messaging with multi-conversation support, real-time events, and JWT authentication.
Features #
- Initialize and display the Zendesk Messaging UI
- JWT user authentication
- Multi-conversation navigation
- Real-time event streaming (24 event types)
- Unread message count tracking
- Conversation tags and custom fields
- Connection status monitoring
- Push notifications support (FCM/APNs)
Requirements #
| Platform | Minimum Version |
|---|---|
| iOS | 14.0 |
| Android | API 21 (minSdk) |
| Dart | 3.6.0 |
| Flutter | 3.27.0 |
Installation #
Add zendesk_messaging to your pubspec.yaml:
dependencies:
zendesk_messaging: <latest_version>
Android Setup #
Add the Zendesk Maven repository to your project-level android/build.gradle:
allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://zendesk.jfrog.io/artifactory/repo' }
}
}
iOS Setup #
Update your ios/Podfile to target iOS 14.0:
platform :ios, '14.0'
Then run:
cd ios && pod install
Quick Start #
Getting Channel Keys #
Before initializing the SDK, you need to obtain your Android and iOS channel keys from the Zendesk Admin Center:
- Go to Admin Center > Channels > Messaging and social > Messaging
- Hover over the brand you want to configure and click the options icon
- Click Edit and navigate to the Installation section
- Under Channel ID, click Copy to copy the key to your clipboard
- Use this key for both Android and iOS initialization
Note: The same Channel ID is used for both platforms. You can create separate channels for Android and iOS if needed.
Initialize #
import 'package:zendesk_messaging/zendesk_messaging.dart';
// Initialize the SDK (call once at app startup)
await ZendeskMessaging.initialize(
androidChannelKey: '<YOUR_ANDROID_CHANNEL_KEY>',
iosChannelKey: '<YOUR_IOS_CHANNEL_KEY>',
);
Show Messaging UI #
// Show the default messaging interface
await ZendeskMessaging.show();
// Show a specific conversation (requires multi-conversation enabled)
await ZendeskMessaging.showConversation('conversation_id');
// Show the conversation list
await ZendeskMessaging.showConversationList();
// Start a new conversation
await ZendeskMessaging.startNewConversation();
User Authentication #
// Login with JWT
try {
final response = await ZendeskMessaging.loginUser(jwt: '<YOUR_JWT_TOKEN>');
print('Logged in: ${response.id}');
} catch (e) {
print('Login failed: $e');
}
// Check login status
final isLoggedIn = await ZendeskMessaging.isLoggedIn();
// Get current user
final user = await ZendeskMessaging.getCurrentUser();
if (user != null) {
print('User ID: ${user.id}');
print('External ID: ${user.externalId}');
print('Auth type: ${user.authenticationType.name}');
}
// Logout
await ZendeskMessaging.logoutUser();
Event Handling #
The SDK provides a unified event stream for all Zendesk events. Use Dart 3 pattern matching to handle specific event types:
ZendeskMessaging.eventStream.listen((event) {
switch (event) {
case UnreadMessageCountChanged(:final totalUnreadCount, :final conversationId):
print('Unread: $totalUnreadCount${conversationId != null ? ' (conversation: $conversationId)' : ''}');
case AuthenticationFailed(:final errorMessage, :final isJwtExpired):
print('Auth failed: $errorMessage (JWT expired: $isJwtExpired)');
if (isJwtExpired) {
// Refresh JWT token
}
case ConnectionStatusChanged(:final status):
print('Connection: ${status.name}');
case ConversationAdded(:final conversationId):
print('Conversation added: $conversationId');
case ConversationStarted(:final conversationId):
print('Conversation started: $conversationId');
case ConversationOpened(:final conversationId):
print('Conversation opened: ${conversationId ?? 'default'}');
case MessagesShown(:final conversationId, :final messages):
print('Messages shown: ${messages.length} in $conversationId');
case SendMessageFailed(:final errorMessage):
print('Send failed: $errorMessage');
case FieldValidationFailed(:final errors):
print('Field validation failed: ${errors.join(', ')}');
case MessagingOpened():
print('Messaging UI opened');
case MessagingClosed():
print('Messaging UI closed');
case ProactiveMessageDisplayed(:final proactiveMessageId):
print('Proactive message displayed: $proactiveMessageId');
case ProactiveMessageClicked(:final proactiveMessageId):
print('Proactive message clicked: $proactiveMessageId');
case ConversationWithAgentRequested(:final conversationId):
print('Agent requested: $conversationId');
case ConversationWithAgentAssigned(:final conversationId):
print('Agent assigned: $conversationId');
case ConversationServedByAgent(:final conversationId, :final agentId):
print('Agent serving: $agentId in $conversationId');
case NewConversationButtonClicked():
print('New conversation button clicked');
case PostbackButtonClicked(:final actionName):
print('Postback clicked: $actionName');
case ArticleClicked(:final articleUrl):
print('Article clicked: $articleUrl');
case ArticleBrowserClicked(:final articleUrl):
print('Article opened in browser: $articleUrl');
case ConversationExtensionOpened(:final extensionUrl):
print('Extension opened: $extensionUrl');
case ConversationExtensionDisplayed(:final extensionUrl):
print('Extension displayed: $extensionUrl');
case NotificationDisplayed(:final conversationId):
print('Notification displayed: $conversationId');
case NotificationOpened(:final conversationId):
print('Notification opened: $conversationId');
}
});
// Start listening for events
await ZendeskMessaging.listenUnreadMessages();
Available Events #
| Event | Description |
|---|---|
UnreadMessageCountChanged |
Unread message count changed |
AuthenticationFailed |
Authentication failed |
ConnectionStatusChanged |
Connection status changed |
ConversationAdded |
New conversation created |
ConversationStarted |
Conversation initiated |
ConversationOpened |
Conversation opened |
MessagesShown |
Messages rendered |
SendMessageFailed |
Message send failed |
FieldValidationFailed |
Field validation failed |
MessagingOpened |
Messaging UI opened |
MessagingClosed |
Messaging UI closed |
ProactiveMessageDisplayed |
Proactive message shown |
ProactiveMessageClicked |
Proactive message clicked |
ConversationWithAgentRequested |
User requested agent |
ConversationWithAgentAssigned |
Agent assigned |
ConversationServedByAgent |
Agent serving |
NewConversationButtonClicked |
New conversation clicked |
PostbackButtonClicked |
Postback button clicked |
ArticleClicked |
Article clicked |
ArticleBrowserClicked |
Article opened in browser |
ConversationExtensionOpened |
Extension opened |
ConversationExtensionDisplayed |
Extension displayed |
NotificationDisplayed |
Push notification shown |
NotificationOpened |
Push notification opened |
Unread Message Count #
// Get current count
final count = await ZendeskMessaging.getUnreadMessageCount();
// Get count for specific conversation
final convCount = await ZendeskMessaging.getUnreadMessageCountForConversation('conv_id');
// Listen to count changes (legacy API)
ZendeskMessaging.unreadMessagesCountStream.listen((count) {
print('Unread: $count');
});
Conversation Tags & Fields #
// Set tags (applied when user sends a message)
await ZendeskMessaging.setConversationTags(['vip', 'mobile', 'flutter']);
// Clear tags
await ZendeskMessaging.clearConversationTags();
// Set custom fields
await ZendeskMessaging.setConversationFields({
'app_version': '3.0.0',
'platform': 'flutter',
'user_tier': 'premium',
});
// Clear fields
await ZendeskMessaging.clearConversationFields();
Connection Status #
final status = await ZendeskMessaging.getConnectionStatus();
// Handle different connection states
final color = switch (status) {
ZendeskConnectionStatus.connected ||
ZendeskConnectionStatus.connectedRealtime => Colors.green,
ZendeskConnectionStatus.connectingRealtime => Colors.orange,
ZendeskConnectionStatus.disconnected => Colors.red,
ZendeskConnectionStatus.unknown => Colors.grey,
};
Push Notifications #
Enable push notifications to notify users of new messages when the app is in the background or closed.
Requirements #
| Platform | Requirement |
|---|---|
| Android | Firebase Cloud Messaging (FCM) setup |
| iOS | APNs certificate uploaded to Zendesk Admin Center |
| Both | Real device (simulators don't support push) |
Setup #
Android
- Add Firebase to your Android app (Firebase setup guide)
- Add
firebase_messagingto yourpubspec.yaml:dependencies: firebase_messaging: ^15.0.0 - Get your FCM Server Key from Firebase Console
- Upload the key to Zendesk Admin Center > Channels > Messaging > Android > Notifications
iOS
- Create an APNs certificate in Apple Developer Portal
- Export as
.p12file from Keychain Access - Upload to Zendesk Admin Center > Channels > Messaging > iOS > Notifications
- Add Push Notifications capability in Xcode
Usage #
import 'dart:io' show Platform;
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:zendesk_messaging/zendesk_messaging.dart';
Future<void> setupPushNotifications() async {
final messaging = FirebaseMessaging.instance;
// Request permission
await messaging.requestPermission();
// Get and register the correct token per platform
// - Android: FCM token via getToken()
// - iOS: APNs device token via getAPNSToken()
// (Zendesk iOS SDK requires APNs token, NOT the FCM token)
await _registerPushToken(messaging);
// Listen for token refresh
// - Android: onTokenRefresh returns new FCM tokens
// - iOS: APNs tokens rarely change (device restore, OS update);
// re-fetch via getAPNSToken() on each app launch (done above).
// onTokenRefresh returns FCM tokens which are NOT usable here.
if (Platform.isAndroid) {
messaging.onTokenRefresh.listen((token) {
ZendeskMessaging.updatePushNotificationToken(token);
});
}
// Handle foreground notifications
FirebaseMessaging.onMessage.listen((message) async {
final responsibility = await ZendeskMessaging.shouldBeDisplayed(message.data);
switch (responsibility) {
case ZendeskPushResponsibility.messagingShouldDisplay:
await ZendeskMessaging.handleNotification(message.data);
case ZendeskPushResponsibility.notFromMessaging:
// Handle your own notification
break;
default:
break;
}
});
// Handle notification tap (app in background)
FirebaseMessaging.onMessageOpenedApp.listen((message) async {
await ZendeskMessaging.handleNotificationTap(message.data);
});
}
Future<void> _registerPushToken(FirebaseMessaging messaging) async {
String? token;
if (Platform.isAndroid) {
token = await messaging.getToken();
} else if (Platform.isIOS) {
token = await messaging.getAPNSToken();
}
if (token != null) {
await ZendeskMessaging.updatePushNotificationToken(token);
}
}
Important (iOS): The Zendesk iOS SDK uses APNs directly for push notifications, not FCM. You must pass the APNs device token via
getAPNSToken(), not the FCM registration token fromgetToken(). Using the wrong token type will silently fail to deliver notifications. APNs tokens rarely change, but callinggetAPNSToken()on each app launch ensures the token stays current.
Push Notification API #
| Method | Returns | Description |
|---|---|---|
updatePushNotificationToken(token) |
Future<void> |
Register FCM/APNs token with Zendesk |
shouldBeDisplayed(data) |
Future<ZendeskPushResponsibility> |
Check if notification is from Zendesk |
handleNotification(data) |
Future<bool> |
Display the notification |
handleNotificationTap(data) |
Future<void> |
Handle notification tap |
ZendeskPushResponsibility #
| Value | Description |
|---|---|
messagingShouldDisplay |
Zendesk notification, SDK can display it |
messagingShouldNotDisplay |
Zendesk notification, but should not display (e.g., user is viewing the conversation) |
notFromMessaging |
Not a Zendesk notification, handle it yourself |
unknown |
Unable to determine |
SDK Lifecycle #
// Check if SDK is initialized
final isInit = await ZendeskMessaging.isInitialized();
// Invalidate SDK instance (cleanup)
await ZendeskMessaging.invalidate();
// After invalidate, you must call initialize() again to use the SDK
API Reference #
ZendeskMessaging #
| Method | Returns | Description |
|---|---|---|
initialize(androidChannelKey, iosChannelKey) |
Future<void> |
Initialize the SDK |
isInitialized() |
Future<bool> |
Check if SDK is initialized |
invalidate() |
Future<void> |
Invalidate SDK instance |
show() |
Future<void> |
Show messaging UI |
showConversation(id) |
Future<void> |
Show specific conversation |
showConversationList() |
Future<void> |
Show conversation list |
startNewConversation() |
Future<void> |
Start new conversation |
loginUser(jwt) |
Future<ZendeskLoginResponse> |
Login with JWT |
logoutUser() |
Future<void> |
Logout current user |
isLoggedIn() |
Future<bool> |
Check login status |
getCurrentUser() |
Future<ZendeskUser?> |
Get current user info |
getUnreadMessageCount() |
Future<int> |
Get total unread count |
getUnreadMessageCountForConversation(id) |
Future<int> |
Get unread count for conversation |
listenUnreadMessages() |
Future<void> |
Start listening for events |
setConversationTags(tags) |
Future<void> |
Set conversation tags |
clearConversationTags() |
Future<void> |
Clear conversation tags |
setConversationFields(fields) |
Future<void> |
Set custom fields |
clearConversationFields() |
Future<void> |
Clear custom fields |
getConnectionStatus() |
Future<ZendeskConnectionStatus> |
Get connection status |
updatePushNotificationToken(token) |
Future<void> |
Register push token |
shouldBeDisplayed(data) |
Future<ZendeskPushResponsibility> |
Check notification source |
handleNotification(data) |
Future<bool> |
Handle push notification |
handleNotificationTap(data) |
Future<void> |
Handle notification tap |
Streams #
| Stream | Type | Description |
|---|---|---|
eventStream |
Stream<ZendeskEvent> |
All Zendesk events |
unreadMessagesCountStream |
Stream<int> |
Unread count changes (legacy) |
Models #
ZendeskUser
class ZendeskUser {
String? id;
String? externalId;
ZendeskAuthenticationType authenticationType;
}
ZendeskLoginResponse
class ZendeskLoginResponse {
String? id;
String? externalId;
}
ZendeskMessage
class ZendeskMessage {
String id;
String conversationId;
String? authorId;
String? content;
DateTime? timestamp;
}
Enums #
ZendeskAuthenticationType
anonymousjwt
ZendeskConnectionStatus
connectedconnectedRealtimeconnectingRealtimedisconnectedunknown
ZendeskPushResponsibility
messagingShouldDisplaymessagingShouldNotDisplaynotFromMessagingunknown
Migration from 2.x #
Breaking Changes in 3.0.0 #
- iOS 14.0 minimum - Apps targeting iOS 12/13 must upgrade
- Dart 3.6+ required - Update your SDK constraint
- Flutter 3.24+ required - Update your Flutter SDK
- New event API - Use sealed class pattern for type-safe event handling
Migration Steps #
- Update
pubspec.yaml:
environment:
sdk: ^3.6.0
flutter: ">=3.27.0"
- Update iOS Podfile:
platform :ios, '14.0'
- Update event handling (optional but recommended):
// Old way (still works)
ZendeskMessaging.unreadMessagesCountStream.listen((count) {
print('Unread: $count');
});
// New way (recommended)
ZendeskMessaging.eventStream.listen((event) {
if (event is UnreadMessageCountChanged) {
print('Unread: ${event.totalUnreadCount}');
}
});
Troubleshooting #
iOS Build Fails #
Ensure your Podfile has the correct platform version:
platform :ios, '14.0'
Then run:
cd ios && pod install --repo-update
Android Build Fails #
Ensure the Zendesk Maven repository is added:
maven { url 'https://zendesk.jfrog.io/artifactory/repo' }
Events Not Received #
Make sure to call listenUnreadMessages() after initialization:
await ZendeskMessaging.initialize(...);
await ZendeskMessaging.listenUnreadMessages();
SDK Versions #
| Plugin | Android SDK | iOS SDK |
|---|---|---|
| 3.1.0 | 2.36.1 | 2.36.0 |
| 3.0.0 | 2.36.1 | 2.36.0 |
| 2.9.x | 2.26.0 | 2.24.0 |
License #
MIT License - see LICENSE for details.
Contributing #
Contributions are welcome! Please read the contributing guidelines before submitting a pull request.
