---
title: Notification Support
slug: b58m-notification-support
docTags: 
createdAt: 2026-09-14T23:58:02.706Z
---

## Kindly notification support in handover

First provide the SDK with the FCM token using:

```kotlin
KindlySDK.saveNotificationToken(token = "firebase_token")
```

## When a push notification is received

Forward the notification to SDK using

```kotlin
KindlySDK.handleNotification(remoteMessage = remoteMessage)
```

The SDK will double-check if notification type is a Kindly notification before handling it.

## Check if a notification is from Kindly

To determine if a notification should be handled by the Kindly SDK or another notification provider, use the following helper method:

```kotlin
val isKindlyNotification = KindlySDK.isKindlyNotification(remoteMessage)
```

This can be particularly useful when integrating with multiple push notification providers. Example usage:

```kotlin
override fun onMessageReceived(remoteMessage: RemoteMessage) {
    if (KindlySDK.isKindlyNotification(remoteMessage)) {
        // Handle with Kindly SDK
        KindlySDK.handleNotification(remoteMessage)
    } else {
        // Handle with another notification provider (e.g., Braze, mParticle)
        otherNotificationProvider.handleNotification(remoteMessage)
    }
}
```

Both calls work before `start()`.

## Process death and cold start

FCM delivers Kindly's data-only messages to `onMessageReceived` even when the app process is not running. In that process nothing has called `KindlySDK.start(...)` yet; the SDK decrypts the push with the key it persisted when the chat registered the device and posts the banner on its own. `shouldHandleNotification` is still asked when your interface is set in this process (after `kill()`); a fresh process has none. A device that never registered has no key and the push is dropped.

## Tapping the notification

Tapping a Kindly banner opens the chat. If the SDK is not started, the tap launches your launcher activity and the chat opens as soon as `start()` runs (a tap older than 60 seconds is dropped). Set `KindlySDK.openChatOnNotificationTap = false` to only launch the app, and read the tap with `KindlyNotificationIntent.read(intent)` in `onCreate` / `onNewIntent` if you want to route it yourself.

## When the SDK Displays the Notification

Kindly pushes are data-only FCM messages with encrypted content; the SDK decrypts each one and posts a system notification with the decrypted title/body — except when the chat conversation is on screen. The display rule is:

| App state  | Active screen                                             | SDK presents the banner?                    |
| ---------- | --------------------------------------------------------- | ------------------------------------------- |
| Background | any                                                       | ✅ Yes                                       |
| Foreground | Chat conversation on screen                               | ❌ No (the user can already see the message) |
| Foreground | Settings, language, image preview, host-app screens, etc. | ✅ Yes                                       |

:::BlockQuote
Heads-up display in the foreground requires the `kindly_channel` notification channel to remain at `IMPORTANCE_HIGH` (the SDK creates it that way, named "Chat messages" in the system settings) and the user to have granted the `POST_NOTIFICATIONS` runtime permission on Android 13+. A push that cannot be shown for either reason is logged as `permission_denied` or `notifications_disabled`.
:::

## Intercepting Notifications (`shouldHandleNotification`)

Every Kindly push that the SDK successfully decrypts while it is started is forwarded to the host app via the `shouldHandleNotification(notification)` method on `KindlySDKInteraction` — regardless of foreground/background state or which screen is on top. Before `start()` (process death) the SDK posts the banner without calling it. Use it to run side effects (analytics, logging, in-app indicators) or to take over the presentation entirely with your own UI.

| Scenario                                            | Result                                                            |
| --------------------------------------------------- | ----------------------------------------------------------------- |
| Interface not set                                   | SDK presents the notification (subject to the display rule above) |
| `shouldHandleNotification` returns `true` (default) | SDK presents the notification (subject to the display rule above) |
| `shouldHandleNotification` returns `false`          | SDK does **not** present anything — your app handles it           |

The interface receives a `ExternalNotification` containing the decrypted fields plus the original `RemoteMessage.data` map for any extra fields your backend attached:

```kotlin
data class ExternalNotification(
    val id: String,                   // decrypted chat_id
    val title: String,                // decrypted title
    val body: String,                 // decrypted body
    val data: Map<String, String>,    // original RemoteMessage.data
)
```

### Implementation

```kotlin
KindlySDK.`interface` = object : KindlySDKInteraction {
    override fun didPressButton(chatButton: ButtonChat, chatLog: List<MessageChat>) {
        // Handle button press if needed
    }

    override fun shouldHandleNotification(notification: ExternalNotification): Boolean {
        // Always log every Kindly notification, regardless of who presents it
        Analytics.track("kindly_notification_received", notification.id)

        // Example: take over presentation when the app is in a custom in-app inbox
        if (isShowingCustomInbox) {
            customInbox.add(notification)
            return false   // SDK does not present its banner
        }

        return true        // let the SDK present the banner
    }
}
```

### Notes

- The method has a default implementation that returns `true`, so you only need to override it if you want to observe or intercept notifications.
- The callback fires for every successfully decrypted Kindly push — it is **not** gated by the SDK's foreground/chat-screen display rule. Use it for analytics that need to run even when the SDK is on screen.
- The polarity matches `shouldHandleLink`: returning `true` means "the SDK should handle this", returning `false` means "I'll handle it myself".
- Returning `false` only suppresses the system notification — the underlying message has already been delivered to the chat session via the websocket / `/latest` endpoint, so it will appear in the chat history when the user opens it.
