Push Notifications

This guide will help you to properly configure Push notifications in Apphud.

Integrating Push notifications in your app lets you use Rules — a powerful feature that helps increase revenue by automatically offering a discount (or showing a screen) to a user at the right moment.

📘

Supported platforms: iOS, Android, Flutter, and React Native

Rule push notifications are supported on:

  • iOS — through APNs
  • Android — through FCM, Android SDK 3.4.0 or later
  • FlutterSDK 3.3.0 or later
  • React Native — with its native Android dependency on Android SDK 3.4.0 or later (iOS through APNs)

Required Steps

  1. Generate an APNs authentication key for iOS or a Firebase service account for Android.
  2. Upload the credentials to Apphud.
  3. Submit the device push token on iOS and Android using the corresponding SDK methods.
  4. Handle incoming push notification payloads on iOS and Android using the corresponding SDK methods.

iOS – Generate Push Notifications Auth Key

Go to the Apple Developer Center, open the "Keys" page, then create a new key by entering a name and enabling "Apple Push Notifications service (APNs)":

1167

Once created, download the .p8 file and store it safely — you will upload it to Apphud. Also copy your Team ID from the Developer Account Membership page.

📘

Note

Auth Key file name has the format: AuthKey_[KEY_ID].p8, where KEY_ID is your Key Identifier. With an auth key, Apphud can send push notifications to all of your apps in the same Apple Developer account, for both sandbox and production. You can reuse the same auth key for your other apps.

Upload Auth Key to Apphud

Go to the Apphud DashboardApp settingsPush notifications (iOS / APNs section).

  1. Upload your APNs Auth Key (.p8) file.
  2. Enter your Team ID.
  3. Save settings.

Set up Push Notifications in the iOS app

Add Push Notifications Capability

Make sure Push Notifications is enabled in the Capabilities section of your app target:

1216

Register for Notifications

In your AppDelegate, register for notifications:

import UserNotifications

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  Apphud.start(apiKey: YOUR_API_KEY)
  registerForNotifications()
  // ... the rest of your code
  return true
}

func registerForNotifications() {
    UNUserNotificationCenter.current().delegate = self
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
        // handle if needed
    }
    UIApplication.shared.registerForRemoteNotifications()
}

Pass Device Token to Apphud

Submit the APNs device token to Apphud:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    Apphud.submitPushNotificationsToken(token: deviceToken, callback: nil)
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    // Error occurred. Check signing / Push Notifications capability.
}

Handle Incoming Push Payload

Handle the payload both when the app is in the foreground and when the user opens a notification:

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    if Apphud.handlePushNotification(apsInfo: response.notification.request.content.userInfo) {
        // Handled by Apphud — usually nothing else to do
    } else {
        // Handle other push notifications
    }
    completionHandler()
}

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    if Apphud.handlePushNotification(apsInfo: notification.request.content.userInfo) {
        // Handled by Apphud — usually nothing else to do
    } else {
        // Handle other push notifications
    }
    completionHandler([]) // empty array skips showing a banner
}

Apphud.handlePushNotification(apsInfo:) returns true if the payload was an Apphud Rule push and was handled by the SDK.

Run the app on a real device and confirm the push token appears on the User page in Apphud.


Android

Android Rules pushes are delivered via Firebase Cloud Messaging (FCM). You need to:

  1. Create a Firebase project and add your Android app
  2. Generate a Firebase Service Account JSON key and upload it to Apphud
  3. Add google-services.json and Firebase Messaging to your Android project
  4. Submit the FCM token to Apphud
  5. Handle incoming push payloads and provide an Activity for Rule screens

Generate Firebase Service Account for FCM

Apphud sends Android pushes using the FCM HTTP v1 API, which requires a Firebase / Google Cloud Service Account JSON key.

  1. Open the Firebase Console and select your project (or create one).
  2. Click the gear icon → Project settings.
  3. Open the Service accounts tab.
  4. Click Generate new private key (or follow the link to manage service accounts in Google Cloud).
  5. Confirm and download the JSON key file. Store it safely — you will upload it to Apphud.
📘

Service Account vs Google Play credentials

This Firebase Service Account JSON is used only for FCM push delivery. It is separate from the Google Play Service Account JSON used for billing validation in Apphud. Do not reuse one for the other unless you intentionally configured a shared account with both sets of permissions.

Make sure Cloud Messaging is enabled for the project (Firebase Console → BuildCloud Messaging, or via Google Cloud APIs).

Upload Firebase Service Account to Apphud

Go to the Apphud DashboardApp settingsPush notifications (Android / FCM section).

  1. Upload the Firebase Service Account JSON key.
  2. Save settings.

Set up Push Notifications in the Android app

1. Add your Android app to Firebase and download google-services.json

  1. In Firebase Console → Project settingsYour apps, add an Android app with your package name.
  2. Download google-services.json.
  3. Place it in your app module root (typically app/google-services.json).

2. Add Google Services plugin and Firebase Messaging

In the project-level build.gradle / settings.gradle, apply the Google Services plugin (versions may vary):

// project-level
plugins {
    id 'com.google.gms.google-services' version '4.4.2' apply false
}

In the app-level build.gradle:

plugins {
    id 'com.android.application'
    id 'com.google.gms.google-services'
}

dependencies {
    implementation platform('com.google.firebase:firebase-bom:33.5.1')
    implementation 'com.google.firebase:firebase-messaging-ktx'
}

3. Request notification permission (Android 13+)

For regular (visible) pushes on Android 13 (API 33)+, request POST_NOTIFICATIONS at runtime. Silent / data-only Rule pushes may still be delivered without showing a system notification, but requesting permission is recommended if you send regular alerts.

4. Create a FirebaseMessagingService

Create a service that:

  • Submits new FCM tokens to Apphud via Apphud.submitPushNotificationsToken
  • Forwards incoming message data to Apphud.handlePushNotification
class MyFirebaseMessagingService : FirebaseMessagingService() {

    override fun onNewToken(token: String) {
        super.onNewToken(token)
        Apphud.submitPushNotificationsToken(token) { success ->
            // optional: log success / failure
        }
    }

    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)
        val handledByApphud = Apphud.handlePushNotification(message.data)
        if (!handledByApphud) {
            // Not an Apphud Rule push — handle your own notifications here
        }
    }
}

Register the service in AndroidManifest.xml:

<service
    android:name=".MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

5. Submit the current FCM token after SDK start

onNewToken is not always called on every launch. After Apphud.start(...), also fetch the current token and submit it:

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        Apphud.start(
            context = this,
            apiKey = "YOUR_API_KEY",
            ruleCallback = ruleCallback,
        )

        FirebaseMessaging.getInstance().token
            .addOnCompleteListener { task ->
                if (!task.isSuccessful) return@addOnCompleteListener
                val token = task.result ?: return@addOnCompleteListener
                Apphud.submitPushNotificationsToken(token)
            }
    }
}

6. Provide an Activity for Rule screens (ApphudRuleCallback)

When a Rule push is handled (or a pending Rule screen should be shown), the Android SDK needs an Activity to present the in-app screen. Pass an ApphudRuleCallback into Apphud.start and implement provideActivity().

Track the currently resumed Activity and return it from the callback:

class MyApplication : Application() {

    @Volatile
    private var currentActivity: Activity? = null

    private val ruleCallback = object : ApphudRuleCallback {
        override fun provideActivity(): Activity? = currentActivity

        override fun shouldPerformRule(rule: Rule): Boolean = true

        override fun shouldShowScreen(rule: Rule): Boolean = true

        // Optional: purchase / survey / dismiss hooks
        // override fun onPurchaseCompleted(rule: Rule, result: ApphudPurchaseResult) { ... }
        // override fun onRulePaywallWithoutScreen(rule: Rule, paywall: ApphudPaywall) { ... }
    }

    override fun onCreate() {
        super.onCreate()
        trackCurrentActivity()

        Apphud.start(
            context = this,
            apiKey = "YOUR_API_KEY",
            ruleCallback = ruleCallback,
        )
    }

    private fun trackCurrentActivity() {
        registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
            override fun onActivityResumed(activity: Activity) {
                currentActivity = activity
            }

            override fun onActivityPaused(activity: Activity) {
                if (currentActivity === activity) currentActivity = null
            }

            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit
            override fun onActivityStarted(activity: Activity) = Unit
            override fun onActivityStopped(activity: Activity) = Unit
            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
            override fun onActivityDestroyed(activity: Activity) = Unit
        })
    }
}
📘

Why provideActivity() matters

If provideActivity() returns null, the SDK falls back to launching the Rule screen in a new task using the application context. Returning the currently visible Activity presents the screen on top of your UI, which is the recommended approach.

That’s it for Android. Run the app, confirm the Push Token is populated on the User page in Apphud, then test a Rule.


Flutter

Flutter SDK 3.3.0 or later supports Rule pushes on both platforms. The dashboard setup is identical: upload the APNs Auth Key + Team ID for iOS and the Firebase Service Account JSON for Android as described above.

Two Dart methods are used, both returning bool:

// iOS: APNs device token as a hex string. Android: FCM registration token.
await Apphud.submitPushNotificationsToken(token);

// iOS: notification `userInfo`. Android: FCM `message.data` (must contain `rule_id`).
// Returns true when Apphud handled the payload as a Rule notification.
await Apphud.handlePushNotification(data);
📘

No ApphudRuleCallback needed on Flutter

The Flutter plugin registers the native rule callback for you and supplies the current Activity on Android, so Rule screens are presented automatically. You do not need to implement provideActivity(). Register Apphud.setRuleListener(...) only if you want to observe rule events (appear, purchase, dismiss, survey) — see Rules.

iOS in a Flutter app

Handle Apphud pushes natively in ios/Runner/AppDelegate.swift. Apphud delivers iOS pushes directly through APNs (not through FCM), so Dart-side push plugins do not reliably surface them.

Keep your AppDelegate subclassing FlutterAppDelegate and call super in the methods you override:

import Flutter
import UIKit
import UserNotifications
import ApphudSDK

@main
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)

        UNUserNotificationCenter.current().delegate = self
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { _, _ in
            DispatchQueue.main.async {
                UIApplication.shared.registerForRemoteNotifications()
            }
        }

        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    override func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        Apphud.submitPushNotificationsToken(token: deviceToken, callback: nil)
        super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
    }

    override func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void
    ) {
        Task { @MainActor in
            if Apphud.handlePushNotification(apsInfo: response.notification.request.content.userInfo) {
                completionHandler()
            } else {
                super.userNotificationCenter(center, didReceive: response, withCompletionHandler: completionHandler)
            }
        }
    }

    override func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        Task { @MainActor in
            if Apphud.handlePushNotification(apsInfo: notification.request.content.userInfo) {
                completionHandler([])
            } else {
                super.userNotificationCenter(center, willPresent: notification, withCompletionHandler: completionHandler)
            }
        }
    }
}

Because Apphud.start() is called from Dart, the APNs token often arrives before the SDK is initialized. Keep the token in your AppDelegate and re-submit it once Apphud.start() completes — the example app does this over a small apphud_example/push method channel.

If you prefer to submit the token from Dart, pass the APNs token as a hex string (for example FirebaseMessaging.instance.getAPNSToken()), not the FCM token:

final apnsToken = await FirebaseMessaging.instance.getAPNSToken();
if (apnsToken != null) {
  await Apphud.submitPushNotificationsToken(apnsToken);
}

Android in a Flutter app

Complete the Android setup above (google-services.json, Google Services plugin, Firebase Messaging dependency) inside example/android / your android folder, then pick one of two approaches.

Option A — native FirebaseMessagingService (recommended)

Add the service under android/app/src/main/kotlin/.... com.apphud.sdk.Apphud is available without extra dependencies, because the Flutter plugin exposes the Android SDK transitively:

package com.example.myapp

import com.apphud.sdk.Apphud
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class MyFirebaseMessagingService : FirebaseMessagingService() {

    override fun onNewToken(token: String) {
        super.onNewToken(token)
        Apphud.submitPushNotificationsToken(token) { success ->
            // optional: log success / failure
        }
    }

    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)
        val handledByApphud = Apphud.handlePushNotification(HashMap<String, Any>(message.data))
        if (!handledByApphud) {
            // Not an Apphud Rule push — handle your own notifications here
        }
    }
}

Register it in android/app/src/main/AndroidManifest.xml inside <application>:

<service
    android:name=".MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

To submit the current token after Apphud.start(), expose a small method channel from MainActivity:

class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "myapp/fcm")
            .setMethodCallHandler { call, result ->
                if (call.method == "submitCurrentToken") {
                    FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
                        val token = task.result
                        if (!task.isSuccessful || token == null) {
                            result.success(false)
                        } else {
                            Apphud.submitPushNotificationsToken(token) { success -> result.success(success) }
                        }
                    }
                } else {
                    result.notImplemented()
                }
            }
    }
}

Option B — forward from Dart with firebase_messaging

If your app already uses the firebase_messaging plugin, do not register a second FirebaseMessagingService — FCM delivers a message to only one service. Forward the token and data payload from Dart instead:

import 'package:apphud/apphud.dart';
import 'package:firebase_messaging/firebase_messaging.dart';

Future<void> setupApphudPush() async {
  await FirebaseMessaging.instance.requestPermission();

  // Submit the current token after Apphud.start() and on every refresh.
  final token = await FirebaseMessaging.instance.getToken();
  if (token != null) {
    await Apphud.submitPushNotificationsToken(token);
  }
  FirebaseMessaging.instance.onTokenRefresh.listen(Apphud.submitPushNotificationsToken);

  // Forward incoming Rule payloads. Apphud Rule pushes contain `rule_id`.
  FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
    final handledByApphud = await Apphud.handlePushNotification(message.data);
    if (!handledByApphud) {
      // Not an Apphud Rule push — handle your own notification here
    }
  });
}
🚧

Background messages

FirebaseMessaging.onBackgroundMessage runs in a separate Dart isolate where the Apphud plugin is not initialized. Handle Apphud Rule payloads either from onMessage (foreground) or from a native FirebaseMessagingService (Option A), which works in all app states.

Dart usage summary

import 'package:apphud/apphud.dart';

await Apphud.start(apiKey: 'YOUR_API_KEY');

// Optional: observe rule lifecycle events. Screens are presented automatically.
await Apphud.setRuleListener(listener: myRuleListener);

// Submit the push token (see platform sections above for how to obtain it).
await Apphud.submitPushNotificationsToken(token);

// Optional helpers
await Apphud.checkRules();               // poll for unread rules right now
final rule = await Apphud.pendingRule();  // pending / displayed rule metadata
await Apphud.showPendingRuleScreen();     // present a delayed rule screen

React Native

The Apphud React Native SDK wraps the native iOS and Android SDKs, so Rule pushes work once the native setup is in place — complete the iOS (APNs) and Android (FCM, Android SDK 3.4.0 or later) steps above for the native side of your app.

Get the device push token from your app's usual React Native push setup — the APNs token on iOS, the FCM registration token on Android — then pass it to Apphud and forward incoming Rule payloads:

import { ApphudSdk } from '@apphud/react-native-apphud-sdk';

// iOS: APNs device token (hex string). Android: FCM registration token.
ApphudSdk.submitPushNotificationsToken(token);

// Forward incoming Rule payloads. Apphud Rule pushes contain `rule_id`.
// iOS: the notification userInfo. Android: the full FCM `message.data` map.
ApphudSdk.handlePushNotification(payload);

See Push Notification Payload for the fields Apphud sends in a Rule push.

Push Notification Payload

Example payload (fields used by Apphud Rules):

{
  "aps": {
    "alert": {
      "title": "Paywall!",
      "body": "TEST"
    },
    "mutable-content": 1,
    "sound": "default"
  },
  "custom": {
    "a": {}
  },
  "rule_id": "5242761d-918d-42ae-a49b-f155b1402c2b",
  "rule_name": "Paywall Custom Rule",
  "screen_id": "0f8b832e-65d3-4777-a303-ba89750763c9",
  "screen_name": "your_screen_name",
  "paywall_id": "0f8b832e-65d3-4777-a303-ba89750763c9",
  "paywall_identifier": "your_screen_name",
}

On Android, Apphud Rule data arrives in RemoteMessage.data (key/value map). Pass that map to Apphud.handlePushNotification(data). On Flutter the same map is passed as Map<String, dynamic>.


Silent Push Notifications

Apphud supports silent push notifications delivered in the background without alerting the user.

Silent pushes:

  • do not require user permission (on iOS) and are designed to be lightweight;
  • do not show alerts, sounds, or banners.

Typical uses:

  • Background data updates (e.g. refreshing subscription status)
  • Quiet in-app logic (preparing personalized content)
  • Updating badge / local storage
  • Supporting Rules automations without interrupting the user

See Rules → Push notification type for details.


Testing Rule push notifications

iOS

Test on a real device (Xcode or TestFlight). The iOS Simulator does not deliver real APNs pushes.

Before testing:

  1. APNs Auth Key is uploaded in Apphud and Team ID is set.
  2. The test user has a valid Push Token on the User page.
  3. Push notifications are allowed on the device.
  4. The app calls Apphud.submitPushNotificationsToken(token:deviceToken, callback: nil).
  5. The app calls Apphud.handlePushNotification(apsInfo: userInfo) for incoming payloads.

Android

Test on a real device or emulator with Google Play Services.

Before testing:

  1. Firebase Service Account JSON is uploaded in Apphud.
  2. google-services.json matches the app package name.
  3. The test user has a valid Push Token on the User page.
  4. The app calls Apphud.submitPushNotificationsToken(token).
  5. The app calls Apphud.handlePushNotification(message.data) from your FirebaseMessagingService.
  6. ApphudRuleCallback.provideActivity() returns the current Activity when a screen should be shown.

Flutter

  1. Use Flutter SDK 3.3.0 or later, and complete the native setup for the platform you are testing.
  2. iOS: test on a real device, and confirm the APNs token is submitted after Apphud.start() (not only when it first arrives).
  3. Android: confirm your FirebaseMessagingService is the one receiving messages, or that Dart forwarding is wired for foreground messages.
  4. The test user has a valid Push Token on the User page.
  5. No ApphudRuleCallback work is required — Rule screens are presented by the plugin.

React Native

  1. Complete the native setup for the platform you're testing — iOS (APNs) and/or Android (FCM, Android SDK 3.4.0 or later).
  2. iOS: test on a real device, and confirm the APNs token is submitted with submitPushNotificationsToken after Apphud.start().
  3. Android: make sure only one FirebaseMessagingService receives the message, and forward the full data map to handlePushNotification.
  4. Confirm the Push Token is populated on the User page in Apphud, then run a Rule test below.

Run a test (all platforms)

  1. Open the Rule → Actions → Test → enter the test User ID.
  2. Wait a few seconds, then refresh the User page in Apphud.
  3. Confirm Last push sent at updated.
  4. Open / bring the app to foreground on the test device — the Rule’s in-app screen should present if one is configured.

Troubleshooting

Push notification doesn't arrive

iOS

  1. Confirm Auth Key, Key ID, and Team ID are set in Apphud.
  2. Confirm Apphud.submitPushNotificationsToken is called with a non-empty token.
  3. Open the User page — Push Token must be populated.

Android

  1. Confirm the Firebase Service Account JSON is uploaded in Apphud Push notifications settings.
  2. Confirm google-services.json is present and package name matches.
  3. Confirm FCM token is submitted via Apphud.submitPushNotificationsToken.
  4. Open the User page — Push Token must be populated.
  5. Confirm Google Play Services are available on the device / emulator.

Flutter

  1. Confirm the push token is submitted after Apphud.start() completed — a token submitted before initialization is not stored.
  2. iOS: confirm you submit the APNs token (hex string or Data), not an FCM token.
  3. Android: confirm only one FirebaseMessagingService is declared in the merged manifest.

Push arrives but is not handled

iOS

  1. Call Apphud.handlePushNotification(apsInfo: userInfo).
  2. Ensure UNUserNotificationCenterDelegate is set.
  3. If using OneSignal or a UNNotificationServiceExtension, verify Apphud still receives the payload.

Android

  1. Call Apphud.handlePushNotification(message.data) inside onMessageReceived.
  2. Pass the data map (RemoteMessage.data), not only notification title/body.
  3. Ensure Apphud.start(...) has already been called before handling the push.
  4. Implement ApphudRuleCallback.provideActivity() so Rule screens can be presented.

Flutter

  1. iOS: handle the payload in AppDelegate — Apphud pushes come straight from APNs and are not routed through Dart push plugins.
  2. iOS: make sure your AppDelegate subclasses FlutterAppDelegate and calls super in overridden lifecycle methods.
  3. Android: FirebaseMessaging.onBackgroundMessage runs in an isolate without the plugin — use a native service instead.
  4. The Apphud Rule payload must contain rule_id; log message.data to verify.

Push tokens are submitted, but users don't receive Rule pushes

  1. Log around token submission and confirm the token is not empty / null.
  2. Confirm Push Token on the User page in Apphud.
  3. Test with the same user / device shown in Apphud.
  4. Confirm notification permission where required (iOS; Android 13+ for visible alerts).
  5. Confirm the Rule’s target audience users actually have push tokens.

If the issue reproduces for a specific user, send Apphud Support the User URL / User ID and the Rule URL.


Using both OneSignal and Apphud Push Notifications

  • iOS: see this guide.
  • Android: if another push SDK also implements FirebaseMessagingService, make sure Apphud still receives FCM tokens and data payloads (either by forwarding from that SDK, or by ensuring your Apphud-aware service is reached). Always call:
    • Apphud.submitPushNotificationsToken(token)
    • Apphud.handlePushNotification(message.data) for Apphud Rule payloads
  • Flutter: the same rule applies — only one FirebaseMessagingService receives each FCM message, so forward tokens and Rule payloads from whichever service or Dart handler your app actually uses.


Did this page help you?