How to add customer support chat to an Android app (Kotlin, 2026)
Most Android apps handle support with a "Contact us" row that opens an email draft. It works, but it throws away everything you need to help: which version they're on, which device, whether they're paying, and which screen they were stuck on. Worse, the reply lands in an inbox they may never check, so the conversation quietly dies.
In-app chat fixes both problems. The user writes from inside the app, you see their context next to the message, and your reply comes back as a push notification that opens the thread. This guide walks through what a good Android implementation needs, whichever SDK or backend you use: where to put the entry point, how to identify users safely, how reply push works with Firebase Cloud Messaging, the Android 13 notification permission, Google Play's account deletion rule, and how to test the whole loop.
Building for iOS too? The same decisions, in Swift, are in how to add customer support chat to an iOS app.
1. Decide where the chat lives
Users look for help in two moments: when something just went wrong, and when they're deciding whether to pay. Put entry points in both places.
- Settings or profile screen. A "Help" or "Chat with us" row. This is where people expect it.
- Error and empty states. If a sync fails or an import produces nothing, a "Need help?" button next to the error catches the user while they still care.
- Near the paywall. Questions like "does this work on my tablet?" or "can I get a refund?" are common right before a purchase. A small help link next to the plans answers them before the user leaves.
Avoid a floating chat bubble over your whole UI. On mobile it covers content and competes with your own buttons. A clear row or button in the right places is enough.
In Compose, the entry point is an ordinary button that calls whatever your SDK uses to open the chat screen:
@Composable
fun HelpRow(onOpenChat: () -> Unit) {
ListItem(
headlineContent = { Text("Chat with us") },
supportingContent = { Text("We reply within a day") },
leadingContent = { Icon(Icons.AutoMirrored.Outlined.HelpOutline, null) },
modifier = Modifier.clickable(onClick = onOpenChat)
)
}
Only promise a reply time you can keep. If you're a solo developer in one time zone, "within a day" is honest; "instantly" isn't.
2. Initialize once, in Application.onCreate()
Support SDKs should be configured in your Application class, not in an Activity. Android can kill and restart your process at any time, and when it recreates a screen it doesn't re-run your launcher activity. Configuring in Application.onCreate() means the SDK is ready whichever screen comes back first, including when the user taps a reply notification.
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
SupportChat.configure(this, appId = BuildConfig.SUPPORT_APP_ID)
}
}
(SupportChat stands in for your SDK's entry point.) Remember to register the class with android:name=".MyApp" on the <application> element in your manifest.
3. Identify users without letting anyone impersonate them
If your app has no accounts, a chat can simply belong to the install. That's fine for many utility apps. But if users sign in, you want one conversation that follows them across a phone and a tablet, and that's where people get identity wrong.
Don't identify users by passing a user ID or email straight from the app. Anything the client sends can be changed, so a curious user could swap in someone else's ID and read their conversation.
Do have your backend sign a short token that proves who the user is, and pass that token to the chat SDK. The common pattern is a JSON Web Token (RFC 7519) signed with a secret that only your server and the chat provider know, with the sub claim set to your user ID:
// After your own sign-in succeeds
val token = api.fetchSupportIdentityToken() // your backend signs this
SupportChat.identify(identityToken = token)
// On sign-out, so the next person on this device starts fresh
SupportChat.reset()
Never ship the signing secret in the APK. Anything in the app binary can be extracted.
4. Send context with every conversation
The most useful thing in-app chat gives you over email is context you didn't have to ask for. Attach what helps you answer:
- App version and build number (
BuildConfig.VERSION_NAME,BuildConfig.VERSION_CODE) - Device model and Android version (
Build.MODEL,Build.VERSION.RELEASE) - The user's plan or entitlement, if you sell subscriptions
- The screen they opened chat from
SupportChat.setMetadata(
mapOf(
"app_version" to BuildConfig.VERSION_NAME,
"plan" to currentPlan.name, // "free", "pro_yearly", …
"opened_from" to "export_error"
)
)
Keep it to what you'd actually use. Don't attach location, contacts or anything you wouldn't want to explain in your privacy policy, because whatever you collect has to be declared in Google Play's Data safety section.
5. Deliver replies with push (and ask for permission properly)
Chat only works if the user finds out you replied. On Android that means Firebase Cloud Messaging (FCM).
Register the token. Fetch the FCM registration token at startup and give it to your chat provider. Firebase's docs note the token can change, for example when the app is restored on a new device, reinstalled, or the user clears app data, so also handle onNewToken in your FirebaseMessagingService (Firebase docs):
FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
SupportChat.setPushToken(token)
}
class AppMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
SupportChat.setPushToken(token)
}
}
Ask for the notification permission at the right moment. Since Android 13 (API level 33), apps need the POST_NOTIFICATIONS runtime permission to show most notifications (Android docs). Declare it in the manifest, but don't fire the system prompt on first launch. A natural moment is right after the user sends their first support message: "Want a notification when we reply?" makes the value obvious.
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted -> /* store the answer; chat still works without it */ }
fun afterFirstMessageSent() {
if (Build.VERSION.SDK_INT >= 33) {
launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
If the user says no, chat should still work. They'll see your reply next time they open the chat screen, so show an unread badge on your help row.
Use a dedicated notification channel. Support replies and marketing pushes are different things. Put replies on their own notification channel ("Support replies") so a user who mutes promotions still hears back from you.
Open the right screen on tap. A reply notification should land the user in the conversation, not on your home screen. Pass the conversation ID in the notification's intent and route to the chat from your activity.
6. Let users delete their data, chat included
If your app lets users create an account, Google Play requires you to offer an in-app path to delete the account and its associated data, and a web link where they can request deletion (Play Console Help). Support conversations are part of that data.
When a user deletes their account, call your chat provider's delete or reset method as part of the same flow, and confirm with the provider that conversation history is removed on their side too. Mention support messages in your Data safety answers.
7. Ask for a review only after you've helped
A solved problem is one of the best moments to ask for a Play Store review, because the user's last experience with you was good. Google's In-App Review API shows the review card inside your app, with two rules worth knowing:
- There's a quota. Google Play enforces a time-bound limit on how often the card appears, and Google doesn't publish the exact value. Calling
launchReviewFlowmore than once in a short period may simply show nothing, so pick the moment carefully. - No pre-questions. Google says your app shouldn't ask "Do you like the app?" or similar questions before or while showing the review card.
So don't gate the request on a "Were you happy?" prompt. Ask once, after a conversation where the user thanked you, and accept that the card may not appear.
8. Test the whole loop before release
Run through this checklist on a real device, with a release build:
- Open chat from each entry point and send a message.
- Confirm the message arrives with version, device and plan attached.
- Sign in on a second device and confirm the same conversation appears.
- Force-stop the app, reply from your side, and confirm the push arrives on the "Support replies" channel.
- Tap the notification and confirm it opens the conversation.
- Deny the notification permission and confirm chat still works with an unread badge.
- Sign out and confirm the next user doesn't see the previous conversation.
- Delete the account and confirm the chat history is gone.
Which SDK should you use?
You can build all of this yourself on Firebase, but most teams use a support SDK so they get the chat UI, inbox and push plumbing for free. When comparing options, check for a native Compose UI (not a web view), a signed-identity option, FCM reply push, a deletion call, and whether pricing is per seat or per monthly active user. Read each vendor's Android docs, not just the marketing page: the minimum SDK level, whether the UI is Compose or a web view, and how push tokens are passed are all spelled out there.
A side-by-side of the native SDKs is in in-app support chat SDKs for iOS and Android.
Disclosure: I built Hailbell, an in-app support chat and push tool for iOS, Android and web. The Android SDK is a native Jetpack Compose library (minSdk 26) that you configure in Application.onCreate() with Hailbell.configure(context, "app_…"), register your FCM token with Hailbell.setPushToken(it), and open with Hailbell.open(activity). An AI answers first from your docs and real prices, and you finish the reply from the dashboard, Discord or Telegram; the user gets a push when you answer. It's $29/month for up to 10,000 monthly active users, with a 14-day free trial and no card required. Setup details are in the docs.
Wrapping up
Good in-app support on Android comes down to a few decisions made carefully: entry points where users get stuck, a signed identity instead of a trusted client ID, context attached automatically, reply push on its own channel with a well-timed permission prompt, and a deletion path that covers chat history. Get those right and support stops being an inbox you dread and becomes the place users decide to stay.
Stuck on a step? Start a chat with us. It's the same chat your users would get.