Set up push notifications in the Genuin SDK
Register device tokens with the Genuin SDK and open the right community, group, or post when a member taps a Genuin notification.
Genuin sends a push notification to a signed-in member when something involves them. Examples are a comment on their post, a mention, a spark, a join request, or a finished AI generation. This page covers the Android and iOS SDKs 2.1.5, the genuin-react-native 3.0.3 package, and the genuin_sdk 1.0.1 Flutter plugin.
Your app owns the Firebase Cloud Messaging (FCM) integration and the notification permission. The SDK sends the device's FCM token to Genuin, recognizes Genuin's notifications by their type field, and opens the screen each one points to.
The steps below summarize how a notification travels from Genuin to the screen it opens:
- Your app gets an FCM token and passes it to the SDK.
- While a member is signed in, the SDK sends the token to Genuin as the device's
push_id. - An event creates a notification. Genuin adds it to the member's in-app notifications list and sends it through FCM to every device registered for that member.
- Android or iOS shows the notification, or hands it to your code when your app is in the foreground.
- When the member taps it, your code asks the SDK whether the notification is Genuin's, and the SDK opens its destination.
Before you begin
| Requirement | Detail |
|---|---|
| SDK modules | Core and UI, version 2.1.5. The UI module holds the screens a notification opens. Without it, willHandleNotification returns false on Android and handleNotifications opens nothing on iOS. |
| Firebase project | One Firebase project with Cloud Messaging that contains your Android app and your iOS app. |
| Genuin connection | Genuin sends to your app's tokens with a service account for your Firebase project, which your Genuin contact connects to your brand (Step 1). |
| Signed-in members | Only members receive notifications. See Choose how members sign in to the Genuin SDK. |
| Android | API 24 or later. Android 13 and later also need the POST_NOTIFICATIONS runtime permission. |
| iOS | iOS 15 or later, with the Push Notifications capability on your app target. |
Note: Genuin's member notifications don't use the push provider or Firebase service account you enter under Outbound Communications > In-App Push in Brand Control Center. They're always sent through FCM with the service account from Step 1.
Step 1: Connect your Firebase project to Genuin
An FCM token only works with the Firebase project that issued it. Genuin therefore sends your members' notifications with credentials for your project, and sends nothing to your app's devices until it has them.
- In the Firebase console, open or create the project for your app, and add your Android and iOS apps to it.
- For iOS, go to Project settings > Cloud Messaging and upload your APNs authentication key under Apple app configuration.
- Go to Project settings > Service accounts and click Generate new private key. Firebase downloads a JSON key file.
- Send the key file to your Genuin contact over a secure channel, and ask them to connect it to your brand.
Caution: The key lets its holder send notifications to every device in your Firebase project. Don't commit it to source control or paste it into a ticket. If it leaks, delete the key in the Google Cloud console and send Genuin a new one.
Step 2: Set up Android
The methods are on GenuinSDK in com.begenuin.begenuin. Initialize the SDK first, as described in Integrate the Genuin Android SDK.
Declare the messaging service and the permission
Add Firebase to the app by following Set up a Firebase Cloud Messaging client app on Android. Then declare your messaging service and the notification permission:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>Add a small notification icon named ic_notification to res/drawable. Genuin's payload names that icon, so Android uses it for notifications that arrive while your app is in the background.
Register the token
Pass the current token to the SDK at every launch, right after initSDK:
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
GenuinSDK.initSDK(this, "<<YOUR_API_KEY>>")
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) GenuinSDK.registerFCMToken(this, task.result)
}
}
}registerFCMToken stores the token on the device. It sends the token to Genuin right away when a member is signed in, and again each time a member signs in with ssoLogin. The token survives sign-out, so you don't need to register it again after a member switches accounts.
Handle messages in the foreground
When your app is in the foreground, Android passes every FCM message to onMessageReceived and shows nothing by itself. Let the SDK build the notification for Genuin's messages, and register refreshed tokens:
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
GenuinSDK.registerFCMToken(this, token)
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
val data = remoteMessage.data
if (GenuinSDK.willHandleForegroundNotification(data)) {
GenuinSDK.handleForegroundNotifications(
context = this,
data = data,
message = remoteMessage.notification?.body ?: "",
title = remoteMessage.notification?.title,
smallNotificationIcon = R.drawable.ic_notification
)
} else {
// Your app's own notifications
}
}
}title has no default value, so pass it even when it's null. The SDK posts the notification on its own channel, ID 101 and named "Notification", at maximum importance with the default sound. Tapping it opens the destination directly, without going through your activity.
Ask for permission and open tapped notifications
When your app is in the background or closed, Android shows Genuin's notification itself. A tap starts your launcher activity with the payload's data keys as intent extras. Hand that intent to the SDK:
class MainActivity : AppCompatActivity() {
private val askNotifications =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
askNotifications.launch(Manifest.permission.POST_NOTIFICATIONS)
}
if (savedInstanceState == null) openGenuinNotification(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
openGenuinNotification(intent)
}
private fun openGenuinNotification(intent: Intent) {
if (GenuinSDK.willHandleNotification(intent)) {
GenuinSDK.handleBackgroundNotifications(context = this, intent = intent)
}
}
}The SDK doesn't ask for POST_NOTIFICATIONS itself. You can move the request to a later moment, such as after a member signs in. The onNewIntent override matters when your launcher activity uses singleTop or singleTask, because Android then reuses the running instance.
Step 3: Set up iOS
Use Firebase Cloud Messaging on iOS too. Genuin sends every notification through FCM, so the token you register must be an FCM token. Initialize the SDK and GenuinUI first, as described in Integrate the Genuin iOS SDK.
Register for notifications and pass the FCM token
Add the FirebaseMessaging package by following Set up a Firebase Cloud Messaging client app on Apple platforms. Then set the delegates in AppDelegate, before launch finishes, so a tap that launches the app isn't lost:
import UIKit
import UserNotifications
import FirebaseCore
import FirebaseMessaging
import GenuinCore
import GenuinUI
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
GenuinUI.shared.initialize()
GenuinSDK.shared.initialize(apiKey: "<<YOUR_API_KEY>>")
FirebaseApp.configure()
Messaging.messaging().delegate = self
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { _, _ in }
application.registerForRemoteNotifications()
return true
}
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
}
extension AppDelegate: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
guard let fcmToken else { return }
GenuinSDK.shared.registerFCMToken(token: fcmToken)
}
}Firebase calls didReceiveRegistrationToken at every launch and whenever the token changes. registerFCMToken sends the token only when it differs from the last one it stored, and only while a member is signed in. When a member signs in later, the SDK sends the stored token then.
Open tapped notifications
iOS shows Genuin's notification when your app isn't in the foreground, and calls didReceive when the member taps it:
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
Messaging.messaging().appDidReceiveMessage(userInfo)
if GenuinSDK.shared.willHandleNotification(userInfo: userInfo),
let viewController = topViewController() {
GenuinSDK.shared.handleNotifications(viewController: viewController)
} else {
// Your app's own notifications
}
completionHandler()
}
private func topViewController() -> UIViewController? {
var top = UIApplication.shared.connectedScenes
.compactMap { ($0 as? UIWindowScene)?.keyWindow }
.first?.rootViewController
while let presented = top?.presentedViewController { top = presented }
return top
}
}willHandleNotification(userInfo:) returns true for Genuin's notification types and stores the payload. handleNotifications(viewController:) shows a loading indicator on that view controller, loads your brand's settings, and presents the destination.
Call completionHandler() yourself. The SDK's own userNotificationCenter methods only store the payload and never call the completion handler.
Present notifications in the foreground
When your app is in the foreground, iOS asks your delegate how to present the notification. Return the options you want for every notification, Genuin's included:
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.banner, .list, .sound])
}Add this method to the same UNUserNotificationCenterDelegate extension. A tap on the banner then goes through didReceive like any other tap.
Caution: Don't call handleNotifications from willPresent. It opens the destination as soon as the notification arrives, without a tap from the member.
Use APNs without Firebase (Optional)
The SDK also accepts a raw APNs device token. Pass isFCMIntegrated: false, and the SDK registers the token as a hex string:
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
GenuinSDK.shared.application(application,
didRegisterForRemoteNotificationsWithDeviceToken: deviceToken,
isFCMIntegrated: false)
}Warning: Genuin sends through FCM, and FCM doesn't deliver to raw APNs tokens. Use this path only if your Genuin contact has set your brand up to receive notifications on a webhook that your own server delivers through APNs.
With isFCMIntegrated: true, the same method has no effect, so FCM apps don't need to call it.
Step 4: Set up React Native
genuin-react-native 3.0.3 wraps the 2.1.5 native SDKs. On Android, JavaScript calls the push functions on GenuinSDKPlugin. On iOS, the package doesn't export those functions to JavaScript, so your AppDelegate handles tokens and taps.
| Function | Argument | Android | iOS |
|---|---|---|---|
| registerFCMToken(token) | The FCM token | Yes | Not exported |
| willHandleForegroundNotifications(payload) | { data, body } | Yes | Not exported |
| handleForegroundNotifications(payload) | { data, body, title, icon } | Yes | Not exported |
| willHandleBackgroundNotifications(message) | The RemoteMessage | Yes | Not exported |
| handleBackgroundNotifications(message) | The RemoteMessage | Yes | Not exported |
Install @react-native-firebase/app and @react-native-firebase/messaging, then mount this controller once, after your first screen renders:
import { useEffect } from 'react';
import { PermissionsAndroid, Platform } from 'react-native';
import messaging, { type FirebaseMessagingTypes } from '@react-native-firebase/messaging';
import { GenuinSDKPlugin } from 'genuin-react-native';
const open = (m: FirebaseMessagingTypes.RemoteMessage | null) => {
if (m && GenuinSDKPlugin.willHandleBackgroundNotifications(m)) GenuinSDKPlugin.handleBackgroundNotifications(m);
};
export default function NotificationController() {
useEffect(() => {
if (Platform.OS !== 'android') return; // iOS: see AppDelegate below
PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
messaging().getToken().then((token) => GenuinSDKPlugin.registerFCMToken(token));
const offRefresh = messaging().onTokenRefresh((token) => GenuinSDKPlugin.registerFCMToken(token));
const offMessage = messaging().onMessage(async (m) => {
const payload = { data: m.data, body: m.notification?.body ?? '', title: m.notification?.title, icon: 'drawable/ic_notification' };
if (GenuinSDKPlugin.willHandleForegroundNotifications(payload)) GenuinSDKPlugin.handleForegroundNotifications(payload);
});
const offOpened = messaging().onNotificationOpenedApp(open);
messaging().getInitialNotification().then(open);
return () => { offRefresh(); offMessage(); offOpened(); };
}, []);
return null;
}icon names your small icon as folder/name; if the resource isn't found, the SDK falls back to a system icon. registerFCMToken needs a running activity, which is why the controller mounts after the first screen.
On iOS, forward the APNs token to Firebase in AppDelegate as in Step 3, and set Messaging.messaging().delegate = self in didFinishLaunchingWithOptions. Then register the FCM token and pass taps through the Objective-C wrapper from Integrate the Genuin SDK with React Native apps:
import FirebaseMessaging
import GenuinCore
extension AppDelegate: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
if let fcmToken { GenuinSDK.shared.registerFCMToken(token: fcmToken) }
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
GenuinModuleObjCWrapper.handleForegroundNotification(["data": response.notification.request.content.userInfo])
completionHandler()
}
}handleForegroundNotification checks the payload first and ignores notifications that aren't Genuin's. For Genuin's, it opens the destination on the top-most view controller after 0.1 seconds.
Step 5: Set up Flutter
genuin_sdk 1.0.1 wraps the older native SDKs, genuin_android_sdk 1.1.9.3 on Android and the GenuinSDK 1.1.9.1 pod on iOS. Its GenuinSdk class exposes the same five functions over a method channel. Add firebase_core and firebase_messaging with the FlutterFire setup, then call this once from the main isolate:
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:genuin_sdk/genuin_sdk.dart';
final genuin = GenuinSdk();
Future<void> setUpGenuinNotifications() async {
final messaging = FirebaseMessaging.instance;
await messaging.requestPermission(alert: true, badge: true, sound: true);
final token = await messaging.getToken();
if (token != null) await genuin.registerFCMToken(token);
messaging.onTokenRefresh.listen(genuin.registerFCMToken);
FirebaseMessaging.onMessage.listen((message) async {
final Map<String, dynamic> payload = {
'data': message.data,
'body': message.notification?.body,
'title': message.notification?.title,
'icon': 'drawable/ic_notification',
};
if (await genuin.willHandleForegroundNotifications(payload) == true) {
await genuin.handleForegroundNotifications(payload);
}
});
Future<void> open(RemoteMessage message) async {
final Map<String, dynamic> payload = {'data': message.data};
if (await genuin.willHandleBackgroundNotifications(payload) == true) {
await genuin.handleBackgroundNotifications(payload);
}
}
FirebaseMessaging.onMessageOpenedApp.listen(open);
final initial = await messaging.getInitialMessage();
if (initial != null) await open(initial);
}On Android, the foreground map must include body and icon; without icon, no notification appears. On iOS, handleForegroundNotifications has no effect, so present banners from your AppDelegate as in Step 3. The Flutter page covers the notification map keys and iOS setup in detail.
Step 6: Test the integration
Test on a physical device with a build that uses your production API key.
- Sign a test member in with
ssoLogin, and grant the notification permission. - From a second member account, comment on a post the test member made in a group. Genuin sends
rt_commentto the post's author. - With your app in the foreground, check that the notification appears and that a tap opens the post with its comments.
- Repeat with your app in the background, and again after swiping it away.
- Open the notifications screen and check that the notification is listed and marked as read.
If nothing arrives on any device, confirm with your Genuin contact that Step 1 is complete before you debug the app.
Notification payload
Each Genuin notification is an FCM message with a notification block, platform options, and a data block. For example, this is the message the test member gets in Step 6:
{
"notification": { "title": "{{TITLE}}", "body": "{{COMMENT_TEXT}}" },
"android": { "notification": { "icon": "ic_notification", "sound": "default" } },
"apns": { "payload": { "aps": { "badge": 1, "sound": "default" } } },
"data": {
"type": "rt_comment",
"notificationId": "{{NOTIFICATION_ID}}",
"sourceId": "{{GROUP_ID}}",
"parentId": "{{POST_ID}}",
"commentId": "{{COMMENT_ID}}"
}
}The SDK routes from the data keys. If your code rebuilds or filters the payload, pass every key through unchanged. All values are strings.
| Key | Sent with | Description |
|---|---|---|
| type | Every notification | What happened. The willHandle… methods compare it with the types the SDK knows. |
| notificationId | Every notification | The notification's ID in the member's list. Opening the notification marks it as read. |
| sourceId | Every notification | The object the notification is about: a group, community, post, or comment, depending on type. |
| parentId | Most types | The object that contains sourceId, such as the post a comment belongs to, or the member who acted. |
| commentId | Comment types | The comment to show. |
| actorId | Group pin types | The member who pinned or unpinned the group. |
aps.badge is always 1, so the iOS app icon shows 1 rather than a running count. The iOS SDK resets the badge to 0 when the member leaves the notifications screen and when they sign out.
Notification types
The SDK opens these types. A tap also marks the notification as read.
| Event | type values | A tap opens |
|---|---|---|
| A new post or repost in the member's group | reply_rt, reposted_rt_to_rt | The post in the group |
| A comment on the member's post, or on a post they commented on | rt_comment, rt_comment_to_other_users | The post with its comments |
| A mention in a comment, or a spark on the member's comment | comment_with_user_mentioned, comment_with_community_mentioned, comment_sparked | The comment |
| A spark on the member's post | post_sparked | The post |
| Group membership and access | invite_rt, remove_rt, delete_video_rt_by_owner, rt_preview_available, rt_participation_request, rt_member_joined | The group's details |
| A group deleted | delete_rt | The community the group belonged to |
| A group pinned or unpinned | group_pinned_group_owner, group_pinned_community_admin, group_unpinned_group_owner, group_unpinned_community_admin | The community |
| Community membership, roles, and visibility | community_join_request, community_join_request_approved, community_member_invited, community_member_joined, community_moderator_invited, community_moderator_removed, new_community_moderator_joined, community_became_private, community_became_public | The community |
| AI generation finished or failed | ai_loop_generated, ai_loop_generation_failed, ai_loop_generation_with_prompt_failed, ai_loop_video_generated, ai_loop_video_generation_failed, ai_loop_video_generation_with_prompt_failed, ai_avatar_voice_generated | The generated community, group, or avatar, or a screen to retry |
| The member was added to a brand's team | bcc_to_cb_added | The brand-upgrade flow for the brand in parentId |
| A community deleted | community_deleted | Nothing beyond your app |
Genuin also sends four types that SDK 2.1.5 doesn't recognize: comment_replied, reply_sparked, reply_with_user_mentioned, and reply_with_community_mentioned. On iOS, rt_member_joined and community_member_joined aren't recognized either. For those, willHandle… returns false and your own handler receives the notification.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| No Genuin notification reaches any device | Genuin has no service account for your Firebase project | Complete Step 1 with your Genuin contact. |
| A member gets notifications on one device but not another | The member isn't signed in on that device. Signing out also archives the device on Genuin's side. | Sign the member in with ssoLogin; the SDK then sends the stored token. |
| Kotlin reports "No value passed for parameter 'title'" | handleForegroundNotifications has no default for title in 2.1.5 | Pass title = remoteMessage.notification?.title. |
| Nothing appears in the foreground on Android 13 or later | POST_NOTIFICATIONS isn't granted | Request the permission, as in Step 2. |
| A tap on Android opens your app but not the content | Your launcher activity doesn't pass the intent to the SDK, or misses onNewIntent | Add openGenuinNotification from Step 2 to both callbacks. |
| willHandleNotification is always false on Android | The UI module isn't in the build | Add com.begenuin:ui at the same version as Core. |
| A tap on iOS opens nothing | GenuinUI.shared.initialize() wasn't called, or no view controller was available | Initialize GenuinUI before GenuinSDK, and pass the top-most view controller. |
| Nothing arrives on iOS with isFCMIntegrated: false | Genuin sends through FCM, which can't use a raw APNs token | Register an FCM token, as in Step 3. |
| The iOS app icon badge stays at 1 | Genuin sets the badge to 1 with every notification | Reset it when your app becomes active, with UNUserNotificationCenter.current().setBadgeCount(0) on iOS 16 and later. |
Limitations
- FCM only: Genuin sends member notifications through Firebase Cloud Messaging. A raw APNs token reaches devices only through a brand webhook that your server delivers.
- Members only: Signed-out viewers receive no notifications. When Genuin has no token for a member, it emails some notification types to members with a verified email address instead.
- Unrecognized types: SDK 2.1.5 doesn't open the four reply types listed under Notification types, and iOS also skips
rt_member_joinedandcommunity_member_joined. - One tap target on Android in the foreground: Every notification
handleForegroundNotificationsposts shares one pending intent. When several are showing, tapping an older one opens the newest one's destination. - Notification IDs on Android: The SDK numbers its foreground notifications from 1 in each process. If your app posts its own notifications with small IDs and no tag, use a tag or a different range so they don't replace each other.
- Sign-out on iOS:
ssoLogoutremoves every delivered and pending notification of your app, your own included, and resets the badge. See Sign members out and handle expired sessions. - React Native on iOS: The JavaScript push functions aren't exported. Tokens and taps go through native code.
- Flutter: The plugin wraps native SDK 1.1.9.x, which doesn't recognize the group pin types,
rt_member_joined,community_member_joined, or the four reply types.

