Send push notifications on iOS
In this tutorial, we make one Mobile Push Notification 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 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 shows how to install either one.
- Your own PubNub keyset. If you don't have one, follow Set up your account 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:
- Sign in to your Apple Developer account and select Certificates, Identifiers & Profiles.
- Select Keys in the sidebar, then select the add button (+) to register a new key.
- Give the key a name, select the Apple Push Notifications service (APNs) checkbox, then select Continue and Register.
- Select Download. You get a file named something like
AuthKeyABCD1234.p8. Apple lets you download it once, so keep it somewhere safe. - Note the Key ID. It's the part of the filename between
AuthKeyand.p8, soAuthKeyABCD1234.p8means the Key ID isABCD1234. The key's detail page in the Keys list shows the same value. - 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:
- Open the Admin Portal and select the app and keyset you're using for this tutorial.
- Scroll to Mobile Push Notifications and turn it on.
- Enter your Team ID and your Auth Key ID, which is the Key ID you noted above.
- Upload the
.p8file 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
- Open your project in Xcode and select the project in the Project navigator, then select your app target.
- Select the Signing & Capabilities tab.
- Select + Capability and add Push Notifications. A Push Notifications section appears in the tab, which is how you know the entitlement is in place.
- 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, because that's where iOS hands you the device token. Start with the PubNub client itself.
- Swift
- Objective-C
1
Two changes to make this yours:
- Replace
demowith the publish key and subscribe key from the keyset you just configured. The shareddemokeyset carries no APNs credentials, so push never arrives on it. - Add
import UserNotificationsandimport UIKitalongside the imports, and keeppubnubas a property of yourAppDelegateso it stays alive for the whole session.
1#import <UIKit/UIKit.h>
2#import <UserNotifications/UserNotifications.h>
3#import <PubNub/PubNub.h>
4
5@interface AppDelegate () <UNUserNotificationCenterDelegate>
6@property (nonatomic, strong) PubNub *client;
7@end
8
9// In application:didFinishLaunchingWithOptions:
10PNConfiguration *configuration = [PNConfiguration configurationWithPublishKey:@"YOUR_PUBLISH_KEY"
11 subscribeKey:@"YOUR_SUBSCRIBE_KEY"
12 userID:@"push-tutorial-user"];
13self.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
- Objective-C
1func application(
2 _ application: UIApplication,
3 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
4) -> Bool {
5
6 UNUserNotificationCenter.current().delegate = self
7
8 UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { granted, error in
9 guard granted else {
10 print("Notification permission was not granted")
11 return
12 }
13 DispatchQueue.main.async {
14 UIApplication.shared.registerForRemoteNotifications()
15 }
show all 19 lines1- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
2
3 UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
4 center.delegate = self;
5
6 [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge)
7 completionHandler:^(BOOL granted, NSError * _Nullable error) {
8 if (!granted) {
9 NSLog(@"Notification permission was not granted");
10 return;
11 }
12 dispatch_async(dispatch_get_main_queue(), ^{
13 [[UIApplication sharedApplication] registerForRemoteNotifications];
14 });
15 }];
show all 18 linesYou'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
- Objective-C
1func application(
2 _ application: UIApplication,
3 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
4) {
5 print("Received device token: \(deviceToken.map { String(format: "%02x", $0) }.joined())")
6 registerDeviceForPush(deviceToken)
7}
8
9func application(
10 _ application: UIApplication,
11 didFailToRegisterForRemoteNotificationsWithError error: Error
12) {
13 print("APNs registration failed: \(error.localizedDescription)")
14}
1- (void)application:(UIApplication *)application
2didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
3 NSLog(@"Received device token: %@", deviceToken);
4 [self registerDeviceForPush:deviceToken];
5}
6
7- (void)application:(UIApplication *)application
8didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
9 NSLog(@"APNs registration failed: %@", error.localizedDescription);
10}
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
- Objective-C
1
Wrap that call in a registerDeviceForPush(_ deviceToken: Data) method, and change four values in it:
- The channel list becomes
["push-tutorial-channel"]. devicebecomes thedeviceTokenthe method receives, rather than the placeholder bytes.onbecomes your bundle identifier, the APNs topic you noted in Xcode.environmentbecomes.development, because you're running a build installed by Xcode. Use.productionfor TestFlight and App Store builds.
1- (void)registerDeviceForPush:(NSData *)deviceToken {
2 [self.client addPushNotificationsOnChannels:@[@"push-tutorial-channel"]
3 withDevicePushToken:deviceToken
4 pushType:PNAPNS2Push
5 environment:PNAPNSDevelopment
6 topic:@"com.yourcompany.yourapp"
7 andCompletion:^(PNAcknowledgmentStatus *status) {
8 if (!status.isError) {
9 NSLog(@"The list of channels added for push: %@", @[@"push-tutorial-channel"]);
10 } else {
11 NSLog(@"Failed Push List Response: %@", status.errorData.information);
12 }
13 }];
14}
Change two values in it:
topicbecomes your bundle identifier, the APNs topic you noted in Xcode.environmentstaysPNAPNSDevelopmentwhile you run a build installed by Xcode. UsePNAPNSProductionfor 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 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:
1{
2 "pn_apns": {
3 "aps": {
4 "alert": {
5 "title": "Apple Message"
6 }
7 },
8 "pn_push": [
9 {
10 "push_type": "alert",
11 "auth_method": "token",
12 "targets": [
13 {
14 "environment": "development",
15 "topic": "com.yourcompany.yourapp"
show all 22 linesBoth 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
- Objective-C
1
Put that in a publishPushMessage() method, and change three values in it:
channelbecomes"push-tutorial-channel", the channel you registered the token on.topicbecomes your bundle identifier.environmentbecomes.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:
1func applicationDidEnterBackground(_ application: UIApplication) {
2 publishPushMessage()
3}
1- (void)publishPushMessage {
2 PNNotificationsPayload *pushData = [PNNotificationsPayload payloadsWithNotificationTitle:@"Apple Message"
3 body:@"Sent from PubNub"];
4
5 PNAPNSNotificationTarget *target = [PNAPNSNotificationTarget targetForTopic:@"com.yourcompany.yourapp"
6 inEnvironment:PNAPNSDevelopment
7 withExcludedDevices:nil];
8
9 PNAPNSNotificationConfiguration *apnsConfig =
10 [PNAPNSNotificationConfiguration configurationWithTargets:@[target]];
11 pushData.apns.configurations = @[apnsConfig];
12
13 NSDictionary *pushPayload = [pushData dictionaryRepresentationFor:PNAPNS2Push];
14
15 [self.client publish:@{@"text": @"Sent from PubNub"}
show all 25 linesChange 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:
1- (void)applicationDidEnterBackground:(UIApplication *)application {
2 [self publishPushMessage];
3}
Run it on your device
-
Connect your iPhone or iPad, select it as the run destination in Xcode, and run the app with ⌘R.
-
Tap Allow on the permission prompt.
-
Watch the Xcode console. Within a second or two you should see two lines, the token first and the registration second:
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 section before going on.
-
Leave the app by swiping up from the bottom of the screen, or by locking the device. That's what triggers the publish.
-
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:
- 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.
- You passed that token to PubNub, paired with the channel name
push-tutorial-channel. PubNub stored the pair. - Your app published a message to
push-tutorial-channelwith apn_apnspayload in it. - 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.p8key you uploaded. - 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
developmentfor the first,productionfor the second. A mismatch makes APNs reject the request withBadDeviceToken, and the publish still succeeds, so nothing in the console tells you. - The topic isn't your bundle identifier. The
topicin 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 noReceived device tokenline, check that permission was granted and thatregisterForRemoteNotifications()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
.p8file uploaded. Confirm your publish and subscribe keys belong to that keyset, and that you replaced anydemovalues. - 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. To confirm the token is still registered on the channel you expect, read Check push device registration.
Next steps
You've delivered a push notification to your own device. From here:
- Mobile push notifications. Understand how the push gateway routes a publish to APNs and FCM, and why the same event can arrive twice.
- Available SDKs. Find the mobile push API reference for your platform.
- Debug push notification messages. Read the
-pndebugcompanion channel to see why a push failed. - Forward push errors and device removals to your endpoint. Send push errors and device-removal events to your endpoint.
- Check push device registration. Confirm which channels a device token is currently registered on.
- Test mobile push notifications externally. Send a push directly through APNs, bypassing PubNub, to rule out a credentials or token problem.
- Send push notifications on Android. Run the same flow with Firebase Cloud Messaging on Android.
- Limits. Check the push payload size limits before you add data to a notification.