Skip to main content
Complete guide to implementing push notifications in React Native with Zixflow using Apple Push Notifications (APNs) or Firebase Cloud Messaging (FCM).
** Track your campaigns:** The SDK automatically tracks delivery, opens, and clicks. For advanced tracking customization and implementation details, see Push Notification Tracking.

Prerequisites

Before implementing push notifications:
  1. Configure push credentials in Zixflow dashboard - Add APNs certificates or FCM Server Key
  2. Complete iOS/Android native setup - Ensure native dependencies are installed
  3. Device tokens must be associated with users - Call identify() before sending notifications
The Zixflow React Native SDK supports deep links and images in push notifications out of the box. Custom action buttons need additional native setup (especially on iOS). See Custom Action Buttons below.

Custom Action Buttons

Named action buttons in a push payload are not registered automatically from JavaScript. You must configure them in native code, then handle clicks and tracking in your app.

Payload format

The action_buttons field is a JSON-encoded string:
"[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"},{\"name\":\"Remind Me\",\"deeplink\":\"\"}]"
Parsed structure:
[
  { "name": "Shop Now", "deeplink": "https://yourapp.com/sale" },
  { "name": "Remind Me", "deeplink": "" }
]

iOS: Register categories at launch

iOS requires action buttons to be pre-registered with UNNotificationCenter when the app starts (for example in AppDelegate):
import UserNotifications

// In application(_:didFinishLaunchingWithOptions:)
let actions = [
  UNNotificationAction(identifier: "ACTION_0", title: "Action 1", options: .foreground),
  UNNotificationAction(identifier: "ACTION_1", title: "Action 2", options: .foreground),
]
let category = UNNotificationCategory(
  identifier: "ZX_2BTN",
  actions: actions,
  intentIdentifiers: [],
  options: []
)
UNUserNotificationCenter.current().setNotificationCategories([category])
Rules:
  • Maximum 2 buttons per notification (iOS system limit)
  • Button index is 0-based (leftmost = index 0)
  • Pre-registered titles can be generic (Action 1, Action 2); campaign button names are tracked via action_name
  • deeplink may be empty — handle that case in your navigation code

Android

Android can attach actions from the notification payload when you build or customize the notification. For custom FirebaseMessagingService handling, parse action_buttons and wire click intents yourself.

Tracking action clicks

For delivery, open, and action-button click tracking (including the full action_buttons format), see Push Notification Tracking.

Platform-Specific Requirements

iOS Requirements

  • APNs or FCM: Choose either Apple Push Notification service or Firebase Cloud Messaging
  • Xcode Capabilities: Push Notifications and Background Modes
  • Notification Service Extension: Required for rich push (images, videos)
  • Physical Device: iOS Simulator cannot receive push notifications

Android Requirements

  • Firebase Project: Required for FCM integration
  • google-services.json: Must be in android/app/ directory
  • Minimum API Level 21: Android 5.0 or higher
  • POST_NOTIFICATIONS Permission: Required for Android 13+ (API 33+)

iOS Setup

You can use either APNs (Apple Push Notification service) or FCM (Firebase Cloud Messaging) on iOS.

Step 1: Enable Push Capabilities in Xcode

  1. Open your project’s .xcworkspace file in Xcode
  2. Select your app target
  3. Go to Signing & Capabilities tab
  4. Click + Capability → Add Push Notifications
  5. Click + Capability → Add Background Modes
  6. Enable Remote notifications under Background Modes

Step 2: Choose APNs or FCM

Option A: APNs (Recommended for iOS-only apps) Update your ios/Podfile:
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

platform :ios, '13.0'

target 'YourApp' do
  config = use_native_modules!

  use_react_native!(:path => config[:reactNativePath])

  # Add Zixflow with APNs support
  pod 'zixflow-reactnative/apn', :path => '../node_modules/zixflow-reactnative'

  # Your other dependencies...
end
Option B: FCM (For cross-platform Firebase apps) Update your ios/Podfile with static linkage:
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

platform :ios, '13.0'

# Required for FCM
use_frameworks! :linkage => :static

target 'YourApp' do
  config = use_native_modules!

  use_react_native!(:path => config[:reactNativePath])

  # Add Zixflow with FCM support
  pod 'zixflow-reactnative/fcm', :path => '../node_modules/zixflow-reactnative'

  # Your other dependencies...
end
Install dependencies:
cd ios
pod install
cd ..
For modern Swift-based React Native apps with APNs: AppDelegate.swift:
import UIKit
import ZixflowMessagingPushAPN

@main
class AppDelegateWithZixflowIntegration: ZixflowAppDelegateWrapper<AppDelegate> {}

class AppDelegate: NSObject, UIApplicationDelegate {

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // Set notification delegate
    UNUserNotificationCenter.current().delegate = self

    // Initialize Zixflow push messaging (APNs)
    MessagingPushAPN.initialize(
      withConfig: MessagingPushConfigBuilder()
        .build()
    )

    return true
  }
}

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

// Initialize with FCM
MessagingPushFCM.initialize(
  withConfig: MessagingPushConfigBuilder()
    .build()
)

Step 4: Configure AppDelegate (Objective-C)

If your React Native app uses Objective-C, create a Swift bridge: Create MyAppPushNotificationsHandler.swift:
import Foundation
import ZixflowMessagingPushAPN

@objc
public class MyAppPushNotificationsHandler: NSObject {

  @objc(setupZixflowClickHandling)
  public func setupZixflowClickHandling() {
    MessagingPushAPN.initialize(
      withConfig: MessagingPushConfigBuilder().build()
    )
  }

  @objc(application:deviceToken:)
  public func application(
    _ application: UIApplication,
    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
  ) {
    MessagingPush.shared.application(
      application,
      didRegisterForRemoteNotificationsWithDeviceToken: deviceToken
    )
  }

  @objc(application:error:)
  public func application(
    _ application: UIApplication,
    didFailToRegisterForRemoteNotificationsWithError error: Error
  ) {
    MessagingPush.shared.application(
      application,
      didFailToRegisterForRemoteNotificationsWithError: error
    )
  }
}
Update AppDelegate.mm:
#import "YourApp-Swift.h"

@implementation AppDelegate

MyAppPushNotificationsHandler* pnHandlerObj;

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  // ... existing initialization code ...

  // Register for remote notifications
  [application registerForRemoteNotifications];

  // Initialize push handling
  pnHandlerObj = [[MyAppPushNotificationsHandler alloc] init];
  [pnHandlerObj setupZixflowClickHandling];

  return YES;
}

- (void)application:(UIApplication *)application
    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  [pnHandlerObj application:application deviceToken:deviceToken];
}

- (void)application:(UIApplication *)application
    didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
  [pnHandlerObj application:application error:error];
}

@end

Step 5: Rich Push with Notification Service Extension

To support rich push notifications (images, videos, GIFs), add a Notification Service Extension: 1. Create Notification Service Extension:
  1. In Xcode: FileNewTarget
  2. Select Notification Service Extension
  3. Name it NotificationServiceExtension
  4. Click Finish (activate scheme when prompted)
2. Add Zixflow to Extension: Update your ios/Podfile to include the extension:
# Main app target
target 'YourApp' do
  # ... existing configuration ...
  pod 'zixflow-reactnative/apn', :path => '../node_modules/zixflow-reactnative'
end

# Notification Service Extension target
target 'NotificationServiceExtension' do
  pod 'zixflow-reactnative-richpush/apn', :path => '../node_modules/zixflow-reactnative'
end
For FCM:
target 'NotificationServiceExtension' do
  pod 'zixflow-reactnative-richpush/fcm', :path => '../node_modules/zixflow-reactnative'
end
3. Install dependencies:
cd ios
pod install
cd ..
4. Update NotificationService.swift:
import UserNotifications
import ZixflowMessagingPushAPN

class NotificationService: UNNotificationServiceExtension {
  var contentHandler: ((UNNotificationContent) -> Void)?
  var bestAttemptContent: UNMutableNotificationContent?

  override func didReceive(
    _ request: UNNotificationRequest,
    withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
  ) {
    self.contentHandler = contentHandler
    bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

    // Initialize SDK for extension
    MessagingPushAPN.initializeForExtension(
      withConfig: MessagingPushConfigBuilder(apiKey: "YOUR_API_KEY")
                .build()
    )

    if let bestAttemptContent = bestAttemptContent {
      // Let Zixflow handle rich push and track delivery
      MessagingPush.shared.didReceive(request, withContentHandler: contentHandler)
    }
  }

  override func serviceExtensionTimeWillExpire() {
    // Called when extension is about to be terminated
    MessagingPush.shared.serviceExtensionTimeWillExpire()

    if let contentHandler = contentHandler,
       let bestAttemptContent = bestAttemptContent {
      contentHandler(bestAttemptContent)
    }
  }
}
Important: Replace "YOUR_API_KEY" with your actual API key.

Android Setup

Android push notifications use Firebase Cloud Messaging (FCM).

Step 1: Add Firebase to Your Project

  1. Go to Firebase Console
  2. Add your Android app to Firebase project
  3. Download google-services.json
  4. Place google-services.json in android/app/ directory

Step 2: Configure Gradle

Project-level android/build.gradle:
buildscript {
    ext {
        minSdkVersion = 21
        compileSdkVersion = 34
        targetSdkVersion = 34
    }

    dependencies {
        classpath 'com.google.gms:google-services:4.4.2'
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}
App-level android/app/build.gradle:
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'  // Add this line

android {
    // ... existing configuration ...
}

dependencies {
    // React Native auto-linking handles Zixflow dependencies
    // ... your other dependencies ...
}

Step 3: Add Permissions

Add to android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Required for push notifications -->
    <uses-permission android:name="android.permission.INTERNET" />

    <!-- Required for Android 13+ (API 33+) -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application>
        <!-- Your app configuration -->
    </application>
</manifest>

Step 4: Configure Notification Icon and Color (Optional)

Add to android/app/src/main/AndroidManifest.xml inside <application> tag:
<application>
    <!-- Default notification icon (white, transparent background) -->
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_icon"
        android:resource="@drawable/ic_notification" />

    <!-- Default notification color (accent color) -->
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_color"
        android:resource="@color/notification_color" />
</application>

React Native Integration

Step 1: Initialize SDK

Initialize the Zixflow SDK in your App.tsx:
import React, { useEffect } from 'react';
import { Zixflow, ZixflowConfig, ZixflowLogLevel } from 'zixflow-reactnative';

export default function App() {
  useEffect(() => {
    // Configure the SDK
    const config: ZixflowConfig = {
      apiKey: 'YOUR_API_KEY',
      logLevel: ZixflowLogLevel.Debug,
      inApp: {
              },
      autoTrackDeviceAttributes: true,
      trackApplicationLifecycleEvents: true,
    };

    // Initialize the SDK
    Zixflow.initialize(config);

    console.log('Zixflow SDK initialized');
  }, []);

  return (
    // Your app UI
  );
}

Step 2: Request Push Permission

Request permission to send push notifications:
import React, { useState } from 'react';
import { Button, Alert } from 'react-native';
import { Zixflow, ZixflowPushPermissionStatus } from 'zixflow-reactnative';

export default function PushPermissionButton() {
  const [permissionStatus, setPermissionStatus] = useState<ZixflowPushPermissionStatus | null>(null);

  const requestPushPermission = async () => {
    try {
      const permission = await Zixflow.pushMessaging.showPromptForPushNotifications({
        ios: {
          sound: true,
          badge: true,
        },
      });

      setPermissionStatus(permission);

      switch (permission) {
        case ZixflowPushPermissionStatus.Granted:
          Alert.alert('Success', 'Push notifications enabled!');
          console.log('Push notifications enabled');
          break;

        case ZixflowPushPermissionStatus.Denied:
          Alert.alert('Denied', 'Push notification permission was denied');
          console.log('Push notifications denied');
          break;

        case ZixflowPushPermissionStatus.NotDetermined:
          // iOS only: permission not yet asked
          console.log('Push notification permission not determined');
          break;
      }
    } catch (error) {
      console.error('Failed to request push permission:', error);
    }
  };

  return (
    <Button
      title="Enable Push Notifications"
      onPress={requestPushPermission}
    />
  );
}
Permission Options (iOS):
OptionTypeDescription
soundbooleanEnable notification sounds
badgebooleanEnable app badge updates

Step 3: Check Permission Status

Check current push permission status:
import { Zixflow, ZixflowPushPermissionStatus } from 'zixflow-reactnative';

const checkPushPermission = async () => {
  try {
    const status = await Zixflow.pushMessaging.getPushPermissionStatus();

    console.log('Push permission status:', status);

    if (status === ZixflowPushPermissionStatus.Granted) {
      console.log('Push notifications are enabled');
    } else if (status === ZixflowPushPermissionStatus.Denied) {
      console.log('Push notifications are disabled');
    } else {
      console.log('Push permission not determined');
    }
  } catch (error) {
    console.error('Failed to get push permission status:', error);
  }
};

Step 4: Identify Users

Critical: Device tokens must be associated with a user before push notifications can be delivered.
import { Zixflow } from 'zixflow-reactnative';

// Identify user when they log in
const handleLogin = async (userId: string, userEmail: string) => {
  try {
    // Identify user with Zixflow
    Zixflow.identify({
      userId: userId,
      traits: {
        email: userEmail,
        firstName: 'John',
        lastName: 'Doe',
        plan: 'premium',
      },
    });

    console.log('User identified:', userId);
  } catch (error) {
    console.error('Failed to identify user:', error);
  }
};
Important Notes:
  • When users start the app, they automatically generate a device token
  • This token links to their Zixflow profile when you call identify()
  • This ensures notifications are delivered to the correct user

Step 5: Get Device Token

Retrieve the registered device token:
import { Zixflow } from 'zixflow-reactnative';

const getDeviceToken = async () => {
  try {
    const token = await Zixflow.pushMessaging.getRegisteredDeviceToken();
    console.log('Device token:', token);
    return token;
  } catch (error) {
    console.error('Failed to get device token:', error);
    return null;
  }
};

Handle deep links from push notifications:

Setup Deep Linking

1. Configure Deep Links (iOS): Add to ios/YourApp/Info.plist:
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>yourapp</string>
    </array>
  </dict>
</array>
2. Configure Deep Links (Android): Add to android/app/src/main/AndroidManifest.xml:
<activity android:name=".MainActivity">
  <!-- Existing intent filter -->

  <!-- Deep link intent filter -->
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />

    <data
      android:scheme="yourapp"
      android:host="*" />
  </intent-filter>

  <!-- Universal Links (optional) -->
  <intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />

    <data
      android:scheme="https"
      android:host="yourapp.com" />
  </intent-filter>
</activity>
Use React Navigation’s linking configuration:
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Linking } from 'react-native';

const Stack = createNativeStackNavigator();

const linking = {
  prefixes: ['yourapp://', 'https://yourapp.com'],
  config: {
    screens: {
      Home: 'home',
      Product: 'product/:id',
      Profile: 'profile',
    },
  },
};

export default function App() {
  return (
    <NavigationContainer linking={linking}>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Product" component={ProductScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Rich Push Notification Payloads

The Zixflow SDK automatically handles rich push notifications. Here’s how to structure payloads:

iOS APNs Payload

{
  "aps": {
    "mutable-content": 1,
    "alert": {
      "title": "New Product Available",
      "body": "Check out our latest premium features"
    },
    "badge": 1,
    "sound": "default"
  },
  "Zixflow": {
    "push": {
      "link": "yourapp://product/123",
      "image": "https://example.com/product-image.jpg"
    }
  }
}

iOS FCM Payload

{
  "message": {
    "apns": {
      "payload": {
        "aps": {
          "mutable-content": 1,
          "alert": {
            "title": "Special Offer",
            "body": "Limited time discount!"
          }
        },
        "Zixflow": {
          "push": {
            "link": "https://yourapp.com/offers",
            "image": "https://example.com/offer.png"
          }
        }
      },
      "headers": {
        "apns-priority": 10
      }
    }
  }
}

Android FCM Payload

{
  "message": {
    "data": {
      "title": "Order Shipped",
      "body": "Your order is on the way!",
      "image": "https://example.com/shipping.jpg",
      "link": "yourapp://orders/456"
    }
  }
}

Testing Push Notifications

1. Verify Device Token Registration

Check if device token is registered:
const verifyPushSetup = async () => {
  try {
    const token = await Zixflow.pushMessaging.getRegisteredDeviceToken();
    const status = await Zixflow.pushMessaging.getPushPermissionStatus();

    console.log('Device token:', token);
    console.log('Permission status:', status);

    if (token && status === ZixflowPushPermissionStatus.Granted) {
      console.log('✓ Push notifications ready');
    } else {
      console.log('✗ Push setup incomplete');
    }
  } catch (error) {
    console.error('Push verification failed:', error);
  }
};

2. Enable Debug Logging

Set log level to debug during development:
const config: ZixflowConfig = {
  apiKey: 'YOUR_API_KEY',
  logLevel: ZixflowLogLevel.Debug,  // Verbose logging
};

Zixflow.initialize(config);

3. Test on Physical Devices

iOS:
  • Push notifications do not work on iOS Simulator
  • Must use physical iOS device
  • Ensure APNs certificate is configured in Zixflow dashboard
Android:
  • Emulator works if Google Play Services installed
  • Physical device always recommended
  • Ensure FCM Server Key is configured in Zixflow dashboard

4. Send Test Push from Zixflow

  1. Log into Zixflow dashboard
  2. Navigate to MessagingPush Notifications
  3. Create test push notification
  4. Send to specific user (using userId from identify())

Troubleshooting

iOS Push Not Received

Symptoms: Push sent but not delivered on iOS Solutions:
  1. Verify Push Notifications capability enabled in Xcode
  2. Check APNs certificate uploaded to Zixflow dashboard
  3. Ensure testing on physical device (not simulator)
  4. Verify user identified: Zixflow.identify({ userId: ... })
  5. Check permission granted: getPushPermissionStatus()
  6. Review logs for errors

Android Push Not Received

Symptoms: Push sent but not delivered on Android Solutions:
  1. Verify google-services.json in android/app/ directory
  2. Check FCM Server Key in Zixflow dashboard
  3. Ensure POST_NOTIFICATIONS permission granted (Android 13+)
  4. Verify Google Services plugin applied
  5. Check device token registered
  6. Review logs for errors

Rich Push Not Working (Images)

Symptoms: Images not loading in notifications iOS Solutions:
  1. Ensure Notification Service Extension properly configured
  2. Verify extension has correct dependencies in Podfile
  3. Check App Groups configuration (if using)
  4. Verify image URL is accessible
  5. Check extension logs for errors
Android Solutions:
  1. Verify image URL is accessible from device
  2. Check internet connectivity
  3. Ensure image format is supported (JPEG, PNG, GIF)

Permission Denied

Symptoms: User denied push permission Solutions:
  1. Explain value before requesting permission
  2. Guide users to Settings to enable manually
  3. Show in-app message explaining benefits
  4. Request permission at appropriate time (after user engagement)

Device Token Not Registered

Symptoms: getRegisteredDeviceToken() returns empty or fails Solutions:
  1. Verify SDK initialized before calling
  2. Check permission granted
  3. Ensure identify() called to associate token
  4. Review native logs for errors
  5. Rebuild app and retry

Best Practices

  1. Request permission thoughtfully - Explain value before asking
  2. Identify users immediately after authentication
  3. Test on physical devices - Simulators have limitations
  4. Use rich push - Images increase engagement
  5. Handle deep links - Provide seamless navigation
  6. Monitor metrics - Track delivery and open rates
  7. Enable debug logging - During development only
  8. Test both platforms - iOS and Android behave differently
  9. Clear identity on logout - Call clearIdentify()
  10. Keep credentials secure - Never commit API keys to git