---
source_url: https://www.pubnub.com/docs/integrations/mobile-push-notifications/send-push-notifications-ios
title: Send push notifications on iOS
updated_at: 2026-09-30T07:20:08.000Z
---

# Send push notifications on iOS

## Documentation index

To discover more PubNub resources:

1. Fetch [PubNub's llms.txt](https://www.pubnub.com/llms-full.txt) for a list of available pages in Markdown format.
2. Identify relevant URLs from that index.
3. Fetch the target pages.

Do not assume a path exists, always check the index first.

In this tutorial, we make one [Mobile Push Notification](https://www.pubnub.com/docs/integrations/mobile-push-notifications/overview.md) land on your own iPhone or iPad. We ask the user for notification permission, then receive the device token that Apple Push Notification service (APNs) issues. We register that token on a single PubNub channel called `push-tutorial-channel`, then publish a message carrying a `pn_apns` payload to that channel. Along the way you meet the APNs credentials PubNub needs, the device token, the APNs topic, and the push payload. Run every step on a physical device, because APNs does not deliver push notifications to the iOS Simulator.

## Before you begin

You need four things:

* A physical iPhone or iPad, and a cable or wireless pairing so Xcode can run your app on it.
* A paid [Apple Developer Program](https://developer.apple.com/programs/) membership. Creating the APNs authentication key in the next section requires one.
* An Xcode project with the PubNub Swift SDK or the PubNub Objective-C SDK added to it. [Quickstart](https://www.pubnub.com/docs/getting-started/quickstart.md) shows how to install either one.
* Your own PubNub keyset. If you don't have one, follow [Set up your account](https://www.pubnub.com/docs/architecture/authentication/set-up-your-account.md) to create it, then come back here. You need its publish key and subscribe key.

## Configure APNs credentials on your keyset

PubNub talks to APNs on your behalf, so APNs credentials live on your keyset rather than in your app. This is a one-time setup, and nothing later in this tutorial delivers a notification until it's done.

First, create the authentication key in your Apple Developer account:

1. Sign in to your [Apple Developer account](https://developer.apple.com/account/) and select **Certificates, Identifiers & Profiles**.
2. Select **Keys** in the sidebar, then select the add button (**+**) to register a new key.
3. Give the key a name, select the **Apple Push Notifications service (APNs)** checkbox, then select **Continue** and **Register**.
4. Select **Download**. You get a file named something like `AuthKeyABCD1234.p8`. Apple lets you download it once, so keep it somewhere safe.
5. Note the Key ID. It's the part of the filename between `AuthKey` and `.p8`, so `AuthKeyABCD1234.p8` means the Key ID is `ABCD1234`. The key's detail page in the **Keys** list shows the same value.
6. Note your Team ID. It appears under **Membership details** in your account, and it's the 10-character string next to your team name.

Now hand those to PubNub:

1. Open the [Admin Portal](https://admin.pubnub.com) and select the app and keyset you're using for this tutorial.
2. Scroll to **Mobile Push Notifications** and turn it on.
3. Enter your **Team ID** and your **Auth Key ID**, which is the Key ID you noted above.
4. Upload the `.p8` file through the **Token File** option, then save the keyset.

Your keyset can now authenticate to APNs. Because these credentials sit at the keyset level, every channel on this keyset shares them.

## Enable push notifications in your Xcode project

1. Open your project in Xcode and select the project in the Project navigator, then select your app target.
2. Select the **Signing & Capabilities** tab.
3. Select **+ Capability** and add **Push Notifications**. A **Push Notifications** section appears in the tab, which is how you know the entitlement is in place.
4. Note the **Bundle Identifier** shown under **Signing**, for example `com.yourcompany.yourapp`. APNs calls this the topic, and you pass it to PubNub in two places later on, so keep it handy.

## Create the PubNub client

Put the code in this tutorial in your [AppDelegate](https://developer.apple.com/documentation/uikit/uiapplicationdelegate), because that's where iOS hands you the device token. Start with the PubNub client itself.

### Swift

```swift
import PubNubSDK
import Foundation

// Initializes a PubNub object with the configuration
let pubnub = PubNub(
  configuration: PubNubConfiguration(
    publishKey: "demo",
    subscribeKey: "demo",
    userId: "myUniqueUserId"
  )
)
```

Two changes to make this yours:

* Replace `demo` with the publish key and subscribe key from the keyset you just configured. The shared `demo` keyset carries no APNs credentials, so push never arrives on it.
* Add `import UserNotifications` and `import UIKit` alongside the imports, and keep `pubnub` as a property of your `AppDelegate` so it stays alive for the whole session.

### Objective-C

```objectivec
#import <UIKit/UIKit.h>
#import <UserNotifications/UserNotifications.h>
#import <PubNub/PubNub.h>

@interface AppDelegate () <UNUserNotificationCenterDelegate>
@property (nonatomic, strong) PubNub *client;
@end

// In application:didFinishLaunchingWithOptions:
PNConfiguration *configuration = [PNConfiguration configurationWithPublishKey:@"YOUR_PUBLISH_KEY"
                                                                subscribeKey:@"YOUR_SUBSCRIBE_KEY"
                                                                      userID:@"push-tutorial-user"];
self.client = [PubNub clientWithConfiguration:configuration];
```

Replace `YOUR_PUBLISH_KEY` and `YOUR_SUBSCRIBE_KEY` with the keys from the keyset you just configured. Holding the client in a property keeps it alive for the whole session.

## Ask for permission and register with APNs

iOS shows a notification only if the user allows it, and APNs issues a device token only if you ask for one. Do both when the app launches. `requestAuthorization` shows the system permission prompt, and `registerForRemoteNotifications()` starts registration with APNs from inside its completion handler, on the main queue.

### Swift

```swift
func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {

  UNUserNotificationCenter.current().delegate = self

  UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { granted, error in
    guard granted else {
      print("Notification permission was not granted")
      return
    }
    DispatchQueue.main.async {
      UIApplication.shared.registerForRemoteNotifications()
    }
  }

  return true
}
```

### Objective-C

```objectivec
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
  center.delegate = self;

  [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge)
                       completionHandler:^(BOOL granted, NSError * _Nullable error) {
    if (!granted) {
      NSLog(@"Notification permission was not granted");
      return;
    }
    dispatch_async(dispatch_get_main_queue(), ^{
      [[UIApplication sharedApplication] registerForRemoteNotifications];
    });
  }];

  return YES;
}
```

You'll see the permission prompt the first time you run the app, and only that first time. If you already dismissed it, delete the app from the device and run again to get a fresh prompt.

## Receive the device token

APNs answers `registerForRemoteNotifications()` by calling one delegate method with a token that identifies this app on this device. Everything downstream depends on that token, so print it and pass it straight to the registration step you write next.

### Swift

```swift
func application(
  _ application: UIApplication,
  didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
  print("Received device token: \(deviceToken.map { String(format: "%02x", $0) }.joined())")
  registerDeviceForPush(deviceToken)
}

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

### Objective-C

```objectivec
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  NSLog(@"Received device token: %@", deviceToken);
  [self registerDeviceForPush:deviceToken];
}

- (void)application:(UIApplication *)application
didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
  NSLog(@"APNs registration failed: %@", error.localizedDescription);
}
```

Notice that the token arrives asynchronously, a moment after launch, and that APNs can hand you a new one at any later launch. That's why the registration call belongs inside this method rather than anywhere that runs earlier.

## Register the device token on a channel

Now tell PubNub that this device wants push for `push-tutorial-channel`. This is the registration that makes the channel and the token a pair: a publish to that channel becomes a push to that device.

### Swift

```swift
// Enable APNS push notifications for a device on a provided channel
pubnub.addAPNSDevicesOnChannels(
  ["channelSwift"],
  device: Data([0x01, 0x02, 0x03, 0x04]), // Replace with actual device token
  on: "com.app.bundle",
  environment: .production
) { result in
  switch result {
  case let .success(channels):
    print("The list of channels added for push: \(channels)")
  case let .failure(error):
    print("Failed Push List Response: \(error.localizedDescription)")
  }
}
```

Wrap that call in a `registerDeviceForPush(_ deviceToken: Data)` method, and change four values in it:

* The channel list becomes `["push-tutorial-channel"]`.
* `device` becomes the `deviceToken` the method receives, rather than the placeholder bytes.
* `on` becomes your bundle identifier, the APNs topic you noted in Xcode.
* `environment` becomes `.development`, because you're running a build installed by Xcode. Use `.production` for TestFlight and App Store builds.

### Objective-C

```objectivec
- (void)registerDeviceForPush:(NSData *)deviceToken {
  [self.client addPushNotificationsOnChannels:@[@"push-tutorial-channel"]
                         withDevicePushToken:deviceToken
                                    pushType:PNAPNS2Push
                                 environment:PNAPNSDevelopment
                                       topic:@"com.yourcompany.yourapp"
                               andCompletion:^(PNAcknowledgmentStatus *status) {
    if (!status.isError) {
      NSLog(@"The list of channels added for push: %@", @[@"push-tutorial-channel"]);
    } else {
      NSLog(@"Failed Push List Response: %@", status.errorData.information);
    }
  }];
}
```

Change two values in it:

* `topic` becomes your bundle identifier, the APNs topic you noted in Xcode.
* `environment` stays `PNAPNSDevelopment` while you run a build installed by Xcode. Use `PNAPNSProduction` for TestFlight and App Store builds.

Registration is per channel and per token, so a device that wants push on ten channels makes ten of these calls. One channel is all we need here.

## Publish a message with a push payload

A push notification is an ordinary [publish](https://www.pubnub.com/docs/pub-sub/publish/overview.md) that carries an extra key. PubNub's push gateway reads `pn_apns`, looks up the device tokens registered on the channel, and forwards an alert request to APNs for each one. A publish without that key travels over pub/sub only and never reaches APNs.

At minimum, `pn_apns` holds an `aps` object with the alert text, and a `pn_push` array that names the APNs topic and environment to target. On the wire, that looks like this:

```json
{
  "pn_apns": {
    "aps": {
      "alert": {
        "title": "Apple Message"
      }
    },
    "pn_push": [
      {
        "push_type": "alert",
        "auth_method": "token",
        "targets": [
          {
            "environment": "development",
            "topic": "com.yourcompany.yourapp"
          }
        ],
        "version": "v2"
      }
    ]
  }
}
```

Both SDKs build that JSON for you from a payload object. Keep the whole payload within the limit.

| Item | Limit |
| --- | --- |
| Push credentials per keyset | 1 APNs certificate and 1 FCM key |
| Push notification payload size | 2 KB for APNs, 4 KB for FCM |

### Swift

```swift
// Publish a message to a channel with APNS and FCM payloads
let pushMessage = PubNubPushMessage(
  apns: PubNubAPNSPayload(
    aps: APSPayload(alert: .object(.init(title: "Apple Message")), badge: 1, sound: .string("default")),
    pubnub: [.init(
      targets: [.init(topic: "com.pubnub.swift", environment: .production)],
      collapseID: "SwiftSDK",
      pushType: .alert
    )],
    payload: "Push Message from PubNub Swift SDK"
  ),
  fcm: PubNubFCMPayload(
    payload: "Push Message from PubNub Swift SDK",
    target: .topic("com.pubnub.swift"),
    notification: FCMNotificationPayload(title: "Android Message"),
    android: FCMAndroidPayload(collapseKey: "SwiftSDK", notification: FCMAndroidNotification(sound: "default"))
  ),
  additional: "Push Message from PubNub Swift SDK"
)

pubnub.publish(
  channel: "my-channel",
  message: pushMessage
) { result in
  switch result {
  case let .success(timetoken):
    print("Message Successfully Published at: \(timetoken)")
  case let .failure(error):
    print("Failed Response: \(error.localizedDescription)")
  }
}
```

Put that in a `publishPushMessage()` method, and change three values in it:

* `channel` becomes `"push-tutorial-channel"`, the channel you registered the token on.
* `topic` becomes your bundle identifier.
* `environment` becomes `.development`, matching the registration call.

The snippet also fills in an `fcm` payload, which serves Android devices registered on the same channel. APNs reads only the `apns` part, so you can leave `fcm` in place or drop it.

Call the method when the app leaves the foreground, so the notification arrives while the app is in the background:

```swift
func applicationDidEnterBackground(_ application: UIApplication) {
  publishPushMessage()
}
```

### Objective-C

```objectivec
- (void)publishPushMessage {
  PNNotificationsPayload *pushData = [PNNotificationsPayload payloadsWithNotificationTitle:@"Apple Message"
                                                                                     body:@"Sent from PubNub"];

  PNAPNSNotificationTarget *target = [PNAPNSNotificationTarget targetForTopic:@"com.yourcompany.yourapp"
                                                               inEnvironment:PNAPNSDevelopment
                                                         withExcludedDevices:nil];

  PNAPNSNotificationConfiguration *apnsConfig =
      [PNAPNSNotificationConfiguration configurationWithTargets:@[target]];
  pushData.apns.configurations = @[apnsConfig];

  NSDictionary *pushPayload = [pushData dictionaryRepresentationFor:PNAPNS2Push];

  [self.client publish:@{@"text": @"Sent from PubNub"}
             toChannel:@"push-tutorial-channel"
     mobilePushPayload:pushPayload
        withCompletion:^(PNPublishStatus *status) {
    if (!status.isError) {
      NSLog(@"Message Successfully Published");
    } else {
      NSLog(@"Failed Response: %@", status.errorData.information);
    }
  }];
}
```

Change the `topic` to your bundle identifier, and keep `PNAPNSDevelopment` matching the environment you registered with.

Call the method when the app leaves the foreground, so the notification arrives while the app is in the background:

```objectivec
- (void)applicationDidEnterBackground:(UIApplication *)application {
  [self publishPushMessage];
}
```

## Run it on your device

1. Connect your iPhone or iPad, select it as the run destination in Xcode, and run the app with **⌘R**.
2. Tap **Allow** on the permission prompt.
3. Watch the Xcode console. Within a second or two you should see two lines, the token first and the registration second:

   ```text
   Received device token: 8f2a1c...
   The list of channels added for push: ["push-tutorial-channel"]
   ```

   If the second line reports a failure instead, the registration didn't reach PubNub. Check the [Troubleshooting](#troubleshooting) section before going on.

4. Leave the app by swiping up from the bottom of the screen, or by locking the device. That's what triggers the publish.
5. Watch the device. A notification titled **Apple Message** appears within a few seconds.

Bring the app back to the foreground and background it again to send another one. The registration from step 3 is still in place, so every trip to the background produces another notification. That's a quick way to see the payload changes you make take effect.

## What happened

You built the two halves of a push notification, a registration and a publish, and PubNub joined them:

1. Your app asked the user for permission, then asked APNs to register it. APNs replied with a device token that identifies this app on this device.
2. You passed that token to PubNub, paired with the channel name `push-tutorial-channel`. PubNub stored the pair.
3. Your app published a message to `push-tutorial-channel` with a `pn_apns` payload in it.
4. PubNub's push gateway saw `pn_apns`, looked up every device token registered on that channel, and sent an alert request to APNs for each one. It authenticated with the `.p8` key you uploaded.
5. APNs delivered the alert to your device, and iOS displayed it because your app was in the background.

The message and the notification are the same publish taking two paths. A subscriber on that channel receives it as a regular message, and a registered device receives it as a notification. That's why an app that is both subscribed and registered can see the same event twice.

## Troubleshooting

If the notification never appears:

* **The app was in the foreground.** iOS hands the notification to your running app instead of showing a banner. Background the app or lock the device, then publish again.
* **The environment doesn't match the build.** A build that Xcode installs gets a sandbox device token, and a TestFlight or App Store build gets a production one. Register and publish with `development` for the first, `production` for the second. A mismatch makes APNs reject the request with `BadDeviceToken`, and the publish still succeeds, so nothing in the console tells you.
* **The topic isn't your bundle identifier.** The `topic` in both the registration call and the push payload must be the exact bundle identifier of the app on the device.
* **Registration ran before the token arrived.** The token is only valid inside `didRegisterForRemoteNotificationsWithDeviceToken`, or later from a copy you kept. If your console shows no `Received device token` line, check that permission was granted and that `registerForRemoteNotifications()` ran on the main queue.
* **The keyset isn't the configured one.** Push works only on a keyset with Mobile Push Notifications enabled and a `.p8` file uploaded. Confirm your publish and subscribe keys belong to that keyset, and that you replaced any `demo` values.
* **Notifications are off for the app.** Check **Settings** > **Notifications** on the device and confirm your app is allowed to notify.

To see the errors APNs returns to PubNub, read [Debug push notification messages](https://www.pubnub.com/docs/integrations/mobile-push-notifications/debug-push-notification-messages.md). To confirm the token is still registered on the channel you expect, read [Check push device registration](https://www.pubnub.com/docs/integrations/mobile-push-notifications/check-push-device-registration.md).

## Next steps

You've delivered a push notification to your own device. From here:

* [Mobile push notifications](https://www.pubnub.com/docs/integrations/mobile-push-notifications/overview.md). Understand how the push gateway routes a publish to APNs and FCM, and why the same event can arrive twice.
* [Available SDKs](https://www.pubnub.com/docs/getting-started/available-sdks.md). Find the mobile push API reference for your platform.
* [Debug push notification messages](https://www.pubnub.com/docs/integrations/mobile-push-notifications/debug-push-notification-messages.md). Read the `-pndebug` companion channel to see why a push failed.
* [Forward push errors and device removals to your endpoint](https://www.pubnub.com/docs/integrations/mobile-push-notifications/set-up-push-webhooks.md). Send push errors and device-removal events to your endpoint.
* [Check push device registration](https://www.pubnub.com/docs/integrations/mobile-push-notifications/check-push-device-registration.md). Confirm which channels a device token is currently registered on.
* [Test mobile push notifications externally](https://www.pubnub.com/docs/integrations/mobile-push-notifications/test-mobile-push-notifications-externally.md). Send a push directly through APNs, bypassing PubNub, to rule out a credentials or token problem.
* [Send push notifications on Android](https://www.pubnub.com/docs/integrations/mobile-push-notifications/send-push-notifications-android.md). Run the same flow with Firebase Cloud Messaging on Android.
* [Limits](https://www.pubnub.com/docs/architecture/limits.md#mobile-push). Check the push payload size limits before you add data to a notification.

Last updated at: 2026-09-30T07:20:08.000Z
