---
source_url: https://www.pubnub.com/docs/general/basics/send-push-notifications
title: Send mobile push notifications
updated_at: 2026-05-25T11:26:07.717Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Send mobile push notifications

PubNub connects to Android Firebase Cloud Messaging (FCM) and iOS Apple Push Notification service (APNs) for push notifications.

Create provider keys and add them in the [Admin Portal](https://admin.pubnub.com/).

FCM also supports iOS. Use one provider or both.

![Mobile Push Notifications view in Admin Portal showing provider key fields and save controls](https://www.pubnub.com/assets/images/push-notifications-85921229869a8b3aa72d7d0fa4cb2836.png)

Before you begin, make sure you have:

* Access to the Admin Portal
* Platform credentials (APNs key for iOS, Firebase key for Android)
* A mobile app that requests push notification permissions

A device push token is a unique ID from APNs or FCM that targets a specific app install.

## Request a device push token

Register each device with its push provider to get a device push token. Refer to the [Android](https://firebase.google.com/docs/cloud-messaging/android/client), [iOS](https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns), and [Firebase for iOS](https://firebase.google.com/docs/cloud-messaging/ios/client) documentation for setup.

## Register and cache the device push token

After you obtain a device push token, ensure your app always uses the current token. Collect, replace, and cache the device push token. For caching, use [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) on iOS and [SharedPreferences](https://developer.android.com/training/data-storage/shared-preferences) on Android.

To create the cache:

### Swift

```swift
let tokenDispatch = DispatchQueue(label: "com.pubnub.deviceToken", attributes: .concurrent)
var cachedToken: Data? {
  get {
    var token: Data?
    tokenDispatch.sync {
      token = UserDefaults.standard.data(forKey: "com.pubnub.deviceToken")
    }
    return token
  }
  set {
    tokenDispatch.async(flags: .barrier) {
      UserDefaults.standard.set(newValue, forKey: "com.pubnub.deviceToken")
    }
  }
}
```

### Objective-C

```objectivec
static dispatch_queue_t deviceTokenDispatch;
static dispatch_queue_t pushChannelsDispatch;

static dispatch_once_t registerCustomDispatchOnceToken;
- (void) registerCustomDispatchQueues {
  dispatch_once(&registerCustomDispatchOnceToken, ^{
    deviceTokenDispatch = dispatch_queue_create("com.pubnub.deviceToken", DISPATCH_QUEUE_CONCURRENT);
    pushChannelsDispatch = dispatch_queue_create("com.pubnub.pushChannels", DISPATCH_QUEUE_CONCURRENT);
  });
}

- (nullable NSData *) cachedToken {
  __block NSData* token;
  dispatch_sync(deviceTokenDispatch, ^{
    token = [[NSUserDefaults standardUserDefaults] dataForKey: @"com.pubnub.deviceToken"];
  });
  return token;
}
- (void) setCachedToken:(nullable NSData *) newValue {
  dispatch_barrier_async(deviceTokenDispatch, ^{
    [[NSUserDefaults standardUserDefaults] setValue:newValue forKey:@"com.pubnub.deviceToken"];
  });
}
```

### Java

```java
public class SharedPreferencesManager {
  private static SharedPreferences sharedPref;
  private SharedPreferencesManager() { }

  public static void init(Context context) {
    if(sharedPref == null)
      sharedPref = context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
  }

  public static final String FCM_DEVICE_TOKEN = "PUBNUB_FCM_DEVICE_TOKEN";
  public static @Nullable String readDeviceToken() {
    return sharedPref.getString(FCM_DEVICE_TOKEN, null)
  }
  public static void writeDeviceToken(String value) {
    SharedPreferences.Editor prefsEditor = sharedPref.edit();
    prefsEditor.putString(FCM_DEVICE_TOKEN, value);
    prefsEditor.apply();
  }

  public static final String FCM_CHANNEL_LIST = "PUBNUB_FCM_CHANNEL_LIST";
  public static @Nullable String[] readChannelList() {
    return (String[]) sharedPref.getStringSet(FCM_CHANNEL_LIST, null).toArray();
  }
  public static void writeChannelList(Set<String>  value) {
    SharedPreferences.Editor prefsEditor = sharedPref.edit();
    prefsEditor.putStringSet(FCM_CHANNEL_LIST, value);
    prefsEditor.apply();
  }
}
```

### Kotlin

```kotlin
class SharedPreferencesManager private constructor()  {
  companion object {
    private val sharePref = SharedPreferencesManager()
    private lateinit var sharedPreferences: SharedPreferences

    private val PLACE_OBJ = "place_obj"
    private val FCM_DEVICE_TOKEN = "PUBNUB_FCM_DEVICE_TOKEN"
    private val FCM_CHANNEL_LIST = "PUBNUB_FCM_CHANNEL_LIST"

    fun getInstance(context: Context): SharedPreferencesManager {
      if (!::sharedPreferences.isInitialized) {
        synchronized(SharedPreferencesManager::class.java) {
          if (!::sharedPreferences.isInitialized) {
            sharedPreferences = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
          }
        }
      }
      return sharePref
    }
  }

  fun readDeviceToken(): String? = sharedPreferences.getString(FCM_DEVICE_TOKEN, null)
  fun writeDeviceToken(value: String?) = sharedPreferences.edit().putString(FCM_DEVICE_TOKEN, value).apply()

  fun readChannelList(): List<String> = sharedPreferences.getStringSet(FCM_CHANNEL_LIST, null)?.toList().orEmpty()
  fun writeChannelList(value: Set<String>?) = sharedPreferences.edit().putStringSet(FCM_CHANNEL_LIST, value).apply()
}
```

Next, update the cached device push token when it changes.

#### Swift

For more information regarding registering token, go to [Apple Docs](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622958-application).

```swift
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  let oldDeviceToken = self.cachedToken
  guard deviceToken != oldDeviceToken else { return }
  self.cachedToken = deviceToken

  updateAPNSDevicesOnChannels(
    pushChannels.allObjects, newDevice: deviceToken, oldDevice: oldDeviceToken,
    on: "com.mycompany.mybundleid",  environment: .production
  ) { result in
    switch result {
    case .success: print("Successfully updated channels with new token")
    case let .failure(error): print("Failed to update device token due to: \(error)")
    }
  }
}
```

#### Objective-C

For more information regarding registering token, go to [Apple Docs](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622958-application?language=objc).

```objectivec
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  NSData *oldDevice = self.cachedToken;
  if ([deviceToken isEqualToData:oldDevice]) { return; }
  self.cachedToken = deviceToken;

  [self updateAPNSDevicesOnChannels:[self.pushChannels allObjects]
                withDevicePushToken:deviceToken
           replacingDevicePushToken:oldDevice
                           pushType:PNAPNS2Push
                        environment:PNAPNSProduction
                              topic:@"com.mycompany.mybundleid"
                      andCompletion:^(PNAcknowledgmentStatus * _Nonnull status) {
    if (!status.isError) {
      NSLog(@"Successfully updated channels with new token");
    } else {
      NSLog(@"Failed to update device token due to: %@", status);
    }
  }];
}
```

#### Java

```java
public class MyFirebaseMessagingService extends FirebaseMessagingService {
  @Override  public void onNewToken(String token) {
    String oldToken = SharedPreferencesManager.readDeviceToken();
    if (token.equals(oldToken)) { return; }
    SharedPreferencesManager.write(token);

    updatePushNotificationsOnChannels(SharedPreferencesManager.readChannelList(), token, oldToken);
  }
}
```

#### Kotlin

```kotlin
override fun onNewToken(token: String) {
  val oldToken = SharedPreferencesManager.getInstance(applicationContext).readDeviceToken()
  if (token == oldToken) { return  }
  SharedPreferencesManager.getInstance(applicationContext).writeDeviceToken(token)

  updatePushNotificationsOnChannels(
    SharedPreferencesManager.getInstance(applicationContext).readChannelList(),
    token,
    oldToken
  )
}
```

## Connect the device to a channel

Initialize your PubNub client with your subscribe and publish keys. Mark specific channels as push channels. In the examples below, `ch1` and `ch2` are added as push channels.

### Swift

```swift
if let deviceToken = (UIApplication.shared.delegate as? AppDelegate)?.cachedToken {
  pubnub.addAPNSDevicesOnChannels(
    ["ch1", "ch2"],
    device: deviceToken,
    on: "com.mycompany.mybundleid",
    environment: .production
  ) { result in
    switch result {
    case let .success(channelsAdded):
      channelsAdded.forEach { (UIApplication.shared.delegate as? AppDelegate)?.pushChannels.update(with: $0) }
    case let .failure(error): print("Failed to add Push due to: \(error.localizedDescription)")
    }
  }
}
```

### Objective-C

```objectivec
NSData *deviceToken = [(AppDelegate *)[[UIApplication sharedApplication] delegate] cachedToken];
if (deviceToken == nil) { return; }

[self.client addPushNotificationsOnChannels:@[@"ch1",@"ch2"]
                        withDevicePushToken:deviceToken
                                   pushType:PNAPNS2Push
                                environment:PNAPNSProduction
                                      topic:@"com.mycompany.mybundleid"
                              andCompletion:^(PNAcknowledgmentStatus *status) {
  if (!status.isError) {
    NSSet* cachedChannels = [(AppDelegate *)[[UIApplication sharedApplication] delegate] pushChannels];
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] setPushChannels:[cachedChannels setByAddingObjectsFromArray:@[@"ch1",@"ch2"]]];
  } else {  NSLog(@"Failed to add Push due to: %@", status); }
}];
```

### Java

```java
String cachedToken = SharedPreferencesManager.readDeviceToken();

pubnub.addPushNotificationsOnChannels()
  .pushType(PNPushType.FCM)
  .deviceId(cachedToken)
  .channels(Arrays.asList("ch1", "ch2", "ch3"))
  .async(result -> { /* check result */ });
```

### Kotlin

```kotlin
SharedPreferencesManager.getInstance(applicationContext).readDeviceToken()?.also { deviceToken ->
  pubnub.addPushNotificationsOnChannels(
    pushType = PNPushType.FCM,
    deviceId = deviceToken,
    channels = listOf("ch1", "ch2", "ch3")
  ).async { result, status ->
    // Handle Response
  }
}
```

## Build the push notification message

Specify APNs (`pn_apns`) or FCM (`pn_fcm`) in the payload. For APNs, set the environment (`development` or `production`) and the app bundle ID (`topic`).

For Apple Push Notification Service:

### JSON

```json
{
   "text": "John invited you to chat",
   "pn_apns": {
      "aps": {
         "alert": {
            "title": "Chat Invitation",
            "body": "John invited you to chat"
         }
      },
      "pn_push":[
         {
            "push_type": "alert",
            "auth_method": "token",
            "targets":[
               {
                  "environment":"production",
                  "topic":"com.mycompany.mybundleid",
                  "excluded_devices": ["device-token1", "device-token2"]
               }
            ],
            "version":"v2"
         }
      ]
   }
}
```

### Swift

```swift
let message = ["text": "John invited you to chat"]

let pushPayload = PubNubPushMessage(
  apns: PubNubAPNSPayload(
    aps: APSPayload(alert: .object(.init(title: "Chat Invitation", body: "John invited you to chat"))),
    pubnub: [.init(targets: [.init(topic: "com.mycompany.mybundleid", environment: .production)])],
  ),
  additional: message
)
```

### Objective-C

```objectivec
NSDictionary *message = @{
  @"text": @"John invited you to chat"
};

PNNotificationsPayload *pushData = [PNNotificationsPayload
                                payloadsWithNotificationTitle:@"Chat Invitation"
                                body:@"John invited you to chat"];

// create the APNs target with topic and environment
PNAPNSNotificationTarget *target = [PNAPNSNotificationTarget
                                    targetForTopic:@"com.mycompany.mybundleid"
                                    inEnvironment:PNAPNSProduction
                                    withExcludedDevices:nil];

// create the APNs config object and add the target
PNAPNSNotificationConfiguration *apnsConfig =
          [PNAPNSNotificationConfiguration configurationWithTargets:@[target]];

// add the APNs config to the main pushData object
pushData.apns.configurations = @[apnsConfig];

// generate the final pushPayload object
NSDictionary *pushPayload = [pushData dictionaryRepresentationFor:PNAPNS2Push|PNFCMPush];
```

For Firebase:

#### Swift

```swift
let message = ["text": "John invited you to chat"]

let pushPayload = PubNubPushMessage(
  fcm: PubNubFCMPayload(
    payload: nil,
    target: .topic("invitations"),
    apns: FCMApnsConfig(
      headers: [
        "apns-push-type": "alert", "apns-topic": "com.mycompany.mybundleid", "apns-priority": "10"
      ],
      payload: APSPayload(alert: .object(.init(title: "Chat Invitation", body: "John invited you to chat")))
    )
  ),
  additional: message
)
```

#### Objective-C

```objectivec
NSDictionary *message = @{
  @"text": @"John invited you to chat"
};

NSDictionary *pushPayload = @{
  @"text": @"John invited you to chat",
  @"pn_fcm": @{
    @"notification": @{
      @"title": @"Chat Invitation",
      @"body": @"John invited you to chat"
    },
    @"apns": @{
      @"payload": @{
        @"aps": @{
          @"alert": @{
            @"title": @"Chat Invitation",
            @"body": @"John invited you to chat"
             }
          },
        },
      @"headers": @{
          @"apns-push-type": @"alert",
          @"apns-topic": @"com.mycompany.mybundleid",
          @"apns-priority": @"10"
        }
      },
    @"pn_exceptions": @[@"device-token1", @"device-token2"]
   }
};
```

#### JSON(Firebase)

```json
{
  "text": "John invited you to chat",
  "pn_fcm": {
    "android": {
      "notification": {
        "title": "Chat Invitation",
        "body": "John invited you to chat"
      }
    },
    "pn_exceptions" : ["device-token1", "device-token2"]
  }
}
```

#### Java

```java
PushPayloadHelper pushPayloadHelper = new PushPayloadHelper();
PushPayloadHelper.FCMPayload fcmPayload = new PushPayloadHelper.FCMPayload();
PushPayloadHelper.FCMPayload.Notification fcmNotification =
  new PushPayloadHelper.FCMPayload.Notification()
    .setTitle("Chat Invitation")
    .setBody("John invited you to chat");

fcmPayload.setNotification(fcmNotification);
pushPayloadHelper.setFcmPayload(fcmPayload);

Map<String, Object> commonPayload = new HashMap<>();
commonPayload.put("text", "John invited you to chat");
pushPayloadHelper.setCommonPayload(commonPayload);

Map<String, Object> pushPayload = pushPayloadHelper.build();
```

#### Kotlin

```kotlin
val pushPayloadHelper = PushPayloadHelper()

val fcmPayload = FCMPayload().apply {
  notification = FCMPayload.Notification().apply {
    title = "Chat Invitation"
    body = "John invited you to chat"
  }
  custom = mapOf(
    "topic" to "invitations",
    "pn_exceptions" to arrayOf("device-token1", "device-token2")
  )
}
pushPayloadHelper.fcmPayload = fcmPayload

pushPayloadHelper.commonPayload = mapOf(
    "John invited you to chat" to "text"
)

val pushPayload = pushPayloadHelper.build()
```

## Send the push notification

Use `publish` to send the push. Publish on the same channels you registered for mobile push.

### Java

```java
pubnub.publish()
  .channel("ch1")
  .message(pushPayload)
  .async(result -> { /* check result */ });
```

### Kotlin

```kotlin
pubnub.publish(
  channel = "ch1",
  message = pushPayload
).async { result ->
  // Handle Response
}
```

### JavaScript

```javascript
//publish on channel
pubnub.publish(
  {
    channel: "ch1"
    message: pushPayload
  },
  function (status, response) {
    // Handle Response
  }
);
```

### Swift

```swift
pubnub.publish(channel: "ch1", message: pushPayload) { result in
  switch result {
  case let .success(timetoken):
    print("Successfully published message at: \(timetoken)")
  case let .failure(error):
    print("Failed to publish due to: \(error)")
  }
}
```

### Objective-C

```objectivec
[self.pubnub publish:message toChannel:@"ch1" mobilePushPayload:pushPayload
      withCompletion:^(PNPublishStatus *status) {
  if (!status.isError) {
    NSLog(@"Successfully published message");
  } else {
    NSLog(@"Failed to publish due to: %@", status);
  }
}];
```