Back to Docs|Push Notificationsv1.0.0
OverviewDashboard setupAndroid SDKiOS SDKSending campaignsAPI referenceTroubleshooting

Push Notifications

Send targeted push notifications to your iOS and Android users — using your own Firebase and Apple APNs credentials. GatiFlow never shares a notification pipeline across customers.

FCM (Firebase)APNsAndroid & iOSCampaignsPer-app credentials

How it works

GatiFlow acts as an orchestration layer between your dashboard and Apple/Google push infrastructure:

1

You upload credentials

Add your Firebase service account JSON (Android/FCM) and your APNs .p8 key (iOS) in App Settings. Credentials are stored encrypted and never sent to the client.

2

Your app registers tokens

The SDK calls GatiFlow's registration endpoint on startup with the FCM token (Android) or APNs token (iOS). Tokens are stored per-app and per-device.

3

You create a campaign

From the Push Notifications dashboard you compose a title + body + optional data payload, then click Send.

4

GatiFlow delivers

GatiFlow loads your stored credentials, signs the request with your Firebase service account or APNs key, and sends directly to FCM / APNs. Invalid tokens are automatically disabled.

Why your own credentials? FCM tokens are tied to the Firebase project they were registered in. Using a shared GatiFlow Firebase project would mean tokens from yourproject can't be targeted. Uploading your own service account keeps you in full control — you can revoke access at any time.

Dashboard setup

Before your app can receive push notifications you must upload credentials for your target platform(s). Navigate to Apps → [Your App] → Settings in the GatiFlow dashboard.

Android — Firebase service account

  1. Open the Firebase Console and select your project.
  2. Go to Project settings → Service accounts.
  3. Click Generate new private key and download the JSON file.
  4. Back in GatiFlow, open App Settings → Firebase (FCM) Credentials.
  5. Upload the downloaded JSON file or paste its contents.
  6. Click Save Firebase credentials. GatiFlow validates the JSON before saving.

The service account must have the Firebase Cloud Messaging Admin role (automatically included when you generate a key from the Firebase Console service accounts page).

iOS — APNs Auth Key (.p8)

  1. Log in to Apple Developer → Certificates, Identifiers & Profiles → Keys.
  2. Create a new key, enable Apple Push Notifications service (APNs), and download the .p8 file.
  3. Note the Key ID (10-character string shown on the download page) and your Team ID (found in the top-right corner of the Developer portal).
  4. In GatiFlow App Settings → APNs Credentials, upload the .p8 file, and fill in Key ID and Team ID.
  5. Click Save APNs credentials.

APNs auth keys (as opposed to certificates) never expire and work for all your apps in the same team. GatiFlow uses JWT-based APNs auth — no annual certificate renewal needed.

Android SDK setup

The Android push integration uses Firebase Messaging. You need to add the Firebase dependency, create a service that extends FirebaseMessagingService, and register the GatiFlow PushService.

Step 1 — Add Firebase Messaging

Make sure your app-level build.gradle.kts includes:

kotlin
dependencies {
    // ... existing deps
    implementation("com.google.firebase:firebase-messaging-ktx:24.0.0")
    implementation("com.github.dmsyudha:gatiflow-android:v1.0.0")
}

Also apply the Google Services plugin in the same file and ensure your project-levelbuild.gradle.kts has the plugin on the classpath:

kotlin
// app/build.gradle.kts
plugins {
    id("com.android.application")
    id("com.google.gms.google-services")  // add this
}

// project/build.gradle.kts
plugins {
    id("com.google.gms.google-services") version "4.4.0" apply false
}

Step 2 — Add google-services.json

Download google-services.json from Firebase Console → Project settings → Your app, and place it in app/ (same level as your app-levelbuild.gradle.kts).

Step 3 — Enable Push Notifications capability

Add the INTERNET permission to yourAndroidManifest.xml (usually auto-merged via the GatiFlow SDK manifest). Also declare your Firebase Messaging service:

xml
<manifest>
    <uses-permission android:name="android.permission.INTERNET" />
    <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>

Step 4 — Create a Firebase Messaging service

Create a class that extends FirebaseMessagingService. When a new FCM token is issued (or refreshed), register it with GatiFlow:

kotlin
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import dev.gatiflow.sdk.GatiFlow

class MyFirebaseMessagingService : FirebaseMessagingService() {

    /**
     * Called when a new FCM token is issued (app install, token refresh, etc.).
     * Register it with GatiFlow so campaigns can reach this device.
     */
    override fun onNewToken(token: String) {
        super.onNewToken(token)
        GatiFlow.push?.registerToken(token)
    }

    /**
     * Called when a push notification arrives while the app is in foreground.
     * Handle it however you like — GatiFlow already delivered it.
     */
    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)
        message.notification?.let { notif ->
            // Show a local notification or update UI
        }
    }
}

Step 5 — Register PushService

Add PushService::class.java to yourGatiFlow.start() services list:

kotlin
import dev.gatiflow.sdk.GatiFlow
import dev.gatiflow.sdk.services.Crashes
import dev.gatiflow.sdk.services.Analytics
import dev.gatiflow.sdk.push.PushService

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()

        GatiFlow.start(
            app = this,
            appToken = "mhub_YOUR_APP_TOKEN",
            services = listOf(
                Crashes::class.java,
                Analytics::class.java,
                PushService::class.java,  // add this
            ),
        )
    }
}

Step 6 — Request notification permission (Android 13+)

Android 13 (API 33) requires a runtime permission for notifications. Request it at an appropriate moment, such as after a user action:

kotlin
// In your Activity or Fragment (API 33+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    requestPermissions(
        arrayOf(Manifest.permission.POST_NOTIFICATIONS),
        REQUEST_CODE_NOTIFICATIONS,
    )
}

// After permission is granted, retrieve the current FCM token and register it
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
    if (task.isSuccessful) {
        GatiFlow.push?.registerToken(task.result)
    }
}

iOS SDK setup

The iOS integration uses native APNs. You request permission, pass the device token fromdidRegisterForRemoteNotificationsWithDeviceTokento GatiFlow, and that's it.

Step 1 — Enable Push Notifications capability

In Xcode, select your app target → Signing & Capabilities → + Capability → search for Push Notifications and add it. Also add Background Modes and enable Remote notifications.

Step 2 — Add PushService to your SDK initialization

In your SwiftUI App struct (orAppDelegate.application(_:didFinishLaunchingWithOptions:)):

swift
import GatiFlow

@main
struct MyApp: App {
    init() {
        GatiFlow.start(
            appToken: "mhub_YOUR_APP_TOKEN",
            services: [
                CrashService(),
                AnalyticsService(),
                PushService(),       // add this
            ]
        )
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

Step 3 — Request permission & register

Request notification permission early in your app flow (e.g. on first launch or after onboarding):

swift
import UserNotifications
import UIKit

func requestPushPermission() {
    UNUserNotificationCenter.current().requestAuthorization(
        options: [.alert, .badge, .sound]
    ) { granted, _ in
        guard granted else { return }
        DispatchQueue.main.async {
            UIApplication.shared.registerForRemoteNotifications()
        }
    }
}

Step 4 — Pass the APNs token to GatiFlow

In your AppDelegate (or viaUIApplicationDelegateAdaptor in SwiftUI):

swift
import UIKit
import GatiFlow

class AppDelegate: NSObject, UIApplicationDelegate {

    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        // Pass the raw APNs token Data — GatiFlow converts it to a hex string
        GatiFlow.push?.registerAPNsToken(deviceToken)
    }

    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        print("APNs registration failed: \(error)")
    }
}

// SwiftUI — wire up AppDelegate
@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    // ...
}

GatiFlow converts the raw Data token to a hex string automatically. Do not convert it yourself before passing it in.

Step 5 — Handle foreground notifications (optional)

swift
import UserNotifications

// Set this early (e.g. in AppDelegate.didFinishLaunchingWithOptions)
UNUserNotificationCenter.current().delegate = self

extension AppDelegate: UNUserNotificationCenterDelegate {
    // Show notification banner even when the app is in foreground
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler:
            @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        completionHandler([.banner, .sound, .badge])
    }
}

Sending campaigns

Once your credentials are configured and at least one device has registered, you can send campaigns from the GatiFlow dashboard.

Creating a campaign

  1. Go to Push Notifications in the dashboard sidebar.
  2. Click New Campaign.
  3. Select the target app, fill in Title and Message.
  4. Optionally add a Data payload — arbitrary key-value pairs delivered to the app.
  5. Click Create to save as a draft, or Send now to deliver immediately.

Data payload

The optional data payload is delivered as a Map<String, String>on Android and a [String: String] dictionary in the APNsuserInfo on iOS. Use it to deep-link into a specific screen or pass context to the notification handler.

json
{
  "screen": "product_detail",
  "product_id": "prod_abc123",
  "promo_code": "SUMMER20"
}

Delivery report

After sending, the campaign status page shows:

FieldMeaning
Sent countNumber of tokens FCM/APNs accepted
Failed countTokens rejected (device uninstalled, token rotated)
Sent atTimestamp when delivery was initiated

Failed tokens are automatically disabled in GatiFlow's device registry so they don't count against future sends. If a user reinstalls the app and registers again, the new token is stored.

API reference

SDK — device registration endpoint

The SDK communicates with the following endpoint. You don't call this directly unless you're building a custom integration.

http
POST /api/sdk/push/register
x-app-token: mhub_YOUR_APP_TOKEN
Content-Type: application/json

{
  "token":    "fcm-or-apns-device-token",
  "platform": "android" | "ios"
}

// Response 200
{ "ok": true }

// Unregister
DELETE /api/sdk/push/register
x-app-token: mhub_YOUR_APP_TOKEN
Content-Type: application/json

{ "token": "fcm-or-apns-device-token" }

Android — PushService API

MethodDescription
GatiFlow.push?.registerToken(token: String)Register an FCM token with GatiFlow
GatiFlow.push?.unregisterToken(token: String)Disable a token (e.g. on logout)

iOS — PushService API

MethodDescription
GatiFlow.push?.registerAPNsToken(_ data: Data)Convert raw APNs Data token and register it
GatiFlow.push?.registerToken(_ token: String, platform: String)Register a pre-converted hex token
GatiFlow.push?.unregisterToken(_ token: String)Disable a token (e.g. on logout)

Troubleshooting

Push credentials not configured error when sending

Go to Apps → [Your App] → Settings and upload your Firebase service account JSON (for Android) or APNs .p8 key (for iOS). The Send button won't work until at least one credential is saved.

Notifications delivered in the dashboard but not received on device

Check that the device registered a token by looking at the device count shown on the campaign. On Android, confirm POST_NOTIFICATIONS permission was granted (required on API 33+). On iOS, confirm the Push Notifications capability is enabled and the correct Bundle ID matches the APNs key.

FCM service account validation fails

The JSON must be a Firebase service account file (type: "service_account") with project_id, client_email, and private_key. Do not use the Web API key from Firebase Console settings — that's a different kind of key.

APNs key validation fails

The .p8 file content must start with -----BEGIN PRIVATE KEY----- or -----BEGIN EC PRIVATE KEY-----. Make sure you're uploading the raw .p8 file, not a certificate or a certificate signing request.

Token registered but 0 devices shown on campaign

Token registration calls are fire-and-forget on the SDK side. Enable SDK debug logging and check that the registration network request returns 200. Also confirm the app token in GatiFlow.start() matches the app you're sending the campaign from.

GatiFlow.push is null on Android

PushService::class.java must be included in the services list passed to GatiFlow.start(). The push accessor returns null when the service is not registered.

← Back to Docs overviewOpen Push Notifications dashboard →