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:

  1. Your app gets an FCM token and passes it to the SDK.
  2. While a member is signed in, the SDK sends the token to Genuin as the device's push_id.
  3. 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.
  4. Android or iOS shows the notification, or hands it to your code when your app is in the foreground.
  5. 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

RequirementDetail
SDK modulesCore 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 projectOne Firebase project with Cloud Messaging that contains your Android app and your iOS app.
Genuin connectionGenuin 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 membersOnly members receive notifications. See Choose how members sign in to the Genuin SDK.
AndroidAPI 24 or later. Android 13 and later also need the POST_NOTIFICATIONS runtime permission.
iOSiOS 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.

  1. In the Firebase console, open or create the project for your app, and add your Android and iOS apps to it.
  2. For iOS, go to Project settings > Cloud Messaging and upload your APNs authentication key under Apple app configuration.
  3. Go to Project settings > Service accounts and click Generate new private key. Firebase downloads a JSON key file.
  4. 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:

AndroidManifest.xml
xml
<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:

YourApplication.kt
kotlin
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:

MyFirebaseMessagingService.kt
kotlin
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:

MainActivity.kt
kotlin
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:

AppDelegate.swift
swift
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:

AppDelegate.swift
swift
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:

AppDelegate.swift
swift
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:

AppDelegate.swift (APNs only)
swift
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.

FunctionArgumentAndroidiOS
registerFCMToken(token)The FCM tokenYesNot exported
willHandleForegroundNotifications(payload){ data, body }YesNot exported
handleForegroundNotifications(payload){ data, body, title, icon }YesNot exported
willHandleBackgroundNotifications(message)The RemoteMessageYesNot exported
handleBackgroundNotifications(message)The RemoteMessageYesNot exported

Install @react-native-firebase/app and @react-native-firebase/messaging, then mount this controller once, after your first screen renders:

NotificationController.tsx
tsx
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:

AppDelegate.swift (React Native)
swift
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:

genuin_notifications.dart
dart
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.

  1. Sign a test member in with ssoLogin, and grant the notification permission.
  2. From a second member account, comment on a post the test member made in a group. Genuin sends rt_comment to the post's author.
  3. With your app in the foreground, check that the notification appears and that a tap opens the post with its comments.
  4. Repeat with your app in the background, and again after swiping it away.
  5. 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:

FCM message for rt_comment
json
{
  "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.

KeySent withDescription
typeEvery notificationWhat happened. The willHandle… methods compare it with the types the SDK knows.
notificationIdEvery notificationThe notification's ID in the member's list. Opening the notification marks it as read.
sourceIdEvery notificationThe object the notification is about: a group, community, post, or comment, depending on type.
parentIdMost typesThe object that contains sourceId, such as the post a comment belongs to, or the member who acted.
commentIdComment typesThe comment to show.
actorIdGroup pin typesThe 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.

Eventtype valuesA tap opens
A new post or repost in the member's groupreply_rt, reposted_rt_to_rtThe post in the group
A comment on the member's post, or on a post they commented onrt_comment, rt_comment_to_other_usersThe post with its comments
A mention in a comment, or a spark on the member's commentcomment_with_user_mentioned, comment_with_community_mentioned, comment_sparkedThe comment
A spark on the member's postpost_sparkedThe post
Group membership and accessinvite_rt, remove_rt, delete_video_rt_by_owner, rt_preview_available, rt_participation_request, rt_member_joinedThe group's details
A group deleteddelete_rtThe community the group belonged to
A group pinned or unpinnedgroup_pinned_group_owner, group_pinned_community_admin, group_unpinned_group_owner, group_unpinned_community_adminThe community
Community membership, roles, and visibilitycommunity_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_publicThe community
AI generation finished or failedai_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_generatedThe generated community, group, or avatar, or a screen to retry
The member was added to a brand's teambcc_to_cb_addedThe brand-upgrade flow for the brand in parentId
A community deletedcommunity_deletedNothing 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

SymptomCauseFix
No Genuin notification reaches any deviceGenuin has no service account for your Firebase projectComplete Step 1 with your Genuin contact.
A member gets notifications on one device but not anotherThe 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.5Pass title = remoteMessage.notification?.title.
Nothing appears in the foreground on Android 13 or laterPOST_NOTIFICATIONS isn't grantedRequest the permission, as in Step 2.
A tap on Android opens your app but not the contentYour launcher activity doesn't pass the intent to the SDK, or misses onNewIntentAdd openGenuinNotification from Step 2 to both callbacks.
willHandleNotification is always false on AndroidThe UI module isn't in the buildAdd com.begenuin:ui at the same version as Core.
A tap on iOS opens nothingGenuinUI.shared.initialize() wasn't called, or no view controller was availableInitialize GenuinUI before GenuinSDK, and pass the top-most view controller.
Nothing arrives on iOS with isFCMIntegrated: falseGenuin sends through FCM, which can't use a raw APNs tokenRegister an FCM token, as in Step 3.
The iOS app icon badge stays at 1Genuin sets the badge to 1 with every notificationReset 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_joined and community_member_joined.
  • One tap target on Android in the foreground: Every notification handleForegroundNotifications posts 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: ssoLogout removes 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.

See also

TRUST AND COMPLIANCE

© 2026 Genuin Inc.

Genuin Footer