On this page
iOS push notification setup has been driving me crazy for the past few days, so I’m writing up everything I learned for anyone else who might run into the same wall.
How iOS remote push notifications work
APNs — short for Apple Push Notification service — is the heart of Apple’s notification system. It’s the server-side service Apple runs that delivers reliable, real-time push notifications to every iOS and OS X device. Each device that registers for push maintains a long-lived connection to APNs, which is how notifications can show up in real time. Notifications still come through even when the target app isn’t running — the classic example being SMS-style alerts.
Every app has to register for push notifications with APNs. APNs returns a DeviceToken — a unique identifier for that device on APNs. The app then hands the DeviceToken back to its own server to be stored for later use, like this:

When your server needs to push a notification to a specific device, it sends the DeviceToken plus a payload in a fixed format (the Push payload) to APNs, and APNs delivers the notification to the device that DeviceToken belongs to. For a single push, the flow looks like this:

For multi-provider pushes, APNs uses the DeviceToken we gave it earlier to route notifications correctly to each device:

Generating the local push certificate
Open Keychain Access on your Mac.

A dialog will pop up asking for your user email.

Enter your Apple developer account name (an email address). Choose “Saved to disk”, click Continue, then Save. The file is named CertificateSigningRequest.certSigningRequest.

Now head to the Apple Developer Center and go to Member Center.

Click any item on the left to drill in:


App ID
First, register an identity for the app you’re building — an App ID. Click the option on the left as shown:

Click Add to go to the registration page. You need an App ID name and a BundleID. Important: the BundleID can’t contain wildcards — wildcards aren’t compatible with push. Down in App Services, check Push Notification.


Click Continue, then confirm and submit. Notice that Push Notification will be shown as “Configurable” — that’s because we haven’t generated the push certificate yet for this App ID. We’ll come back to check the App ID’s state once the push certificate is ready.

Certificates
Next, generate the developer certificate and the push certificate. As shown below, go to the Certificates section on the left, click Add, and continue to the next page:

If the option is greyed out, it means you already have a developer certificate and don’t need to generate another one. If it’s selectable, pick that option:

On the next page, pick the App ID you registered earlier, then click Continue:

Then choose the CSR file you saved locally (CertificateSigningRequest.certSigningRequest), click Generate, and you’ve created the developer certificate.

The process for the push test certificate is the same — the only difference is that on the certificate-type page you choose Apple Push Notification service SSL.

With the push certificate generated, head back to the App ID you created earlier and you’ll see that Push Notifications is now Enabled. Once the distribution push certificate is also configured, Distribution will show as Enabled too.

Provisioning Profiles
Step three: generate a Provisioning Profile. The profile is basically a bundle that packages up the certificate, App ID, and device list. You generate different types of Provisioning Profiles for different scenarios, and they get embedded into the .ipa when you package the app.
Go to Provisioning Profiles > All on the left, click Add:

Then pick the type. I’ll use Development here as the example — there are two Distribution variants below it:

Click Continue, then pick the push-enabled App ID we created earlier:

Next, pick the developer certificate we generated (this is one-to-one — if you’re generating a Distribution profile, you’ll see distribution certificates here instead):

Then pick the authorized devices — the devices you’re developing on. You can add devices in the Devices section on the left; you need each device’s UUID. Here I’m picking all devices and clicking Continue:

Finally, give the Provisioning Profile a name:

Click Generate and you have your Provisioning Profile.
By the same process, you can generate the Distribution developer certificate, push certificate, and matching Provisioning Profile. Drop all the final files into one folder, as shown — I generated both Distribution variants (Ad Hoc and Distribution) at the same time.

The Push.p12 file is the one we’ll come back to later.
Setting up the dev environment
Install the ios_development.cer developer certificate and the aps_development.cer push certificate on the Mac where you generated the CSR — just double-click each one. Keychain Access will open. Find the private key generated alongside the CSR (it’ll be named after the email you entered when generating the CSR), and select both the private key and the freshly installed push certificate. Important: select both at once — you need to export the private key together with the installed push certificate as a single file. Right-click and choose Export.

Name it Push and click Save:

You’ll be asked to set a password on the certificate. This is the password your server side will need.

Finally, configure the dev environment — meaning Xcode. Open Xcode > Preferences:

Under Accounts, add your developer account (it’ll already be listed if you’ve signed in before). Click View Details:

This pane shows the account’s certificates and Provisioning Profiles. It should match what you see in the developer portal — if it doesn’t, hit refresh:

After a moment, the Provisioning Profile we just created will show up. Then in Xcode under Build Settings -> Code Signing, pick the Provisioning Profile you want.

At this point the local dev environment is set up. Time to code. Code, code, code…
Implementing remote push notifications in code
First, register for push notifications and get a DeviceToken:
- (void)initPushNotificationWithApp: (UIApplication*)application {
// Register for notifications
if([UIDevice currentDevice].systemVersion.floatValue < 8.0) {
[application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge
| UIRemoteNotificationTypeSound
| UIRemoteNotificationTypeAlert)];
} else {
// The registration API changed in iOS 8.0+
UIUserNotificationSettings* settings = [UIUserNotificationSettings settingsForTypes:(UIRemoteNotificationTypeBadge
| UIRemoteNotificationTypeSound
| UIRemoteNotificationTypeAlert)
categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];
}
}
If registration succeeds, APNs returns your device token, and iOS hands it to the app delegate:
// If registration succeeds we get a DeviceToken — send it to the server to be stored
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
PRINT_FUNC
NSString* tokenStr = [NSString stringWithFormat:@"%@", deviceToken];
NSLog(@"deviceToken: %@", tokenStr);
if(tokenStr.length == 0)
{
NSLog(@"Device Token Invalid!");
}
// Hand the DeviceToken to our own server for storage
[self sendDeviceToken:deviceToken];
}
// If registration fails we get an error with the reason
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
PRINT_FUNC
NSLog(@"***************************************\n");
NSLog(@"Failed to Register The Notification!!!!\n");
NSLog(@"error = %@", error);
NSLog(@"***************************************\n");
}
Then in your AppDelegate, handle incoming pushes. When the user taps a notification from the notification center or the app is already running, the - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo delegate method fires:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
PRINT_FUNC
NSLog(@"Received push notification: %@", userInfo);
// userInfo is a dictionary; the exact keys and values are agreed on between client and server
NSString* orderId = [userInfo objectForKey:@"carryOrderId"];
NSLog(@"Received order notification, orderId: %@", orderId);
// ... do whatever you need with the info
}
There’s also this one:
/*! This delegate method offers an opportunity for applications with the "remote-notification" background mode to fetch appropriate new data in response to an incoming remote notification. You should call the fetchCompletionHandler as soon as you're finished performing that operation, so the system can accurately estimate its power and data cost.
This method will be invoked even if the application was launched or resumed because of the remote notification. The respective delegate methods will be invoked first. Note that this behavior is in contrast to application:didReceiveRemoteNotification:, which is not called in those cases, and which will not be invoked if this method is implemented. !*/
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler;
In practice both fire when the app is in the foreground or background — i.e., still alive — but the difference shows up when the app hasn’t launched yet or has been killed in the background. In that case, tapping the notification launches the app, but only the second method fires; the first does not. The comment also tells you that this method will fire even when the app is suspended or fully closed in response to a remote notification, and that it takes precedence over the first one. The second method also lets you exchange data with the server — for example, if an order’s status has changed, you can request the latest order info from your server inside this method.
The official docs describe both methods crisply:
// Tells the delegate that the running app received a remote notification.
- application:didReceiveRemoteNotification:
// Tells the app that a remote notification arrived that indicates there is data to be fetched.
- application:didReceiveRemoteNotification:fetchCompletionHandler:
The server side
Apple’s APNs endpoints:
- Sandbox:
gateway.sandbox.push.apple.com:2195 - Production:
gateway.push.apple.com:2195
The simple notification format, sent as binary, network byte order:

You can implement the server side with raw sockets, but here I’m using the Javapns open-source library — it’s easier, the payload format is already wrapped, and you just call add, add, add. The code uses the Push.p12 we generated earlier:
package testApplePush;
import java.util.List;
import javapns.Push;
import javapns.notification.PushNotificationPayload;
import javapns.notification.PushedNotifications;
public class testApplePush
{
public static void main(String[] args)
{
try
{
PushNotificationPayload payload = new PushNotificationPayload();
payload.addAlert("This is a push notification!"); // body
payload.addBadge(1); // badge number
payload.addSound("default"); // sound
// custom info
payload.addCustomDictionary("carryOrderId", "121212121212121212121212");
// DeviceToken stored on the server
String deviceToken = "************************************************";
PushedNotifications notifications = Push.payload(payload, // the custom payload
"Push.p12", // the certificate we generated
"111111111", // the password set when exporting
false, // send to production?
deviceToken); // the client's DeviceToken
int numOfFailedNotifications = notifications.getFailedNotifications()
.size();
int numOfSuccessfulNotificatios = notifications
.getSuccessfulNotifications().size();
System.out.println(String.format(
"Successful Send: %d, Failed Send: %d",
numOfSuccessfulNotificatios, numOfFailedNotifications));
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
That sends a single push. Javapns doesn’t seem to handle batch sending particularly well. The certificate used here is the .p12 we exported earlier (works on Windows too — some tutorials claim Windows doesn’t recognize it, which isn’t true). Next, here’s a PHP version for reference. It needs the .p12 converted to .pem format. Here’s how:
- Put the
aps_developement.cerpush certificate and thePush.p12in the same folder; - In Terminal,
cdinto that folder, then convertaps_developement.certo.pem. This producesPushCert.pem:
openssl x509 -in aps_development.cer -inform der -out PushCert.pem
- Next, convert
Push.p12to.pem, producingPushKey.pem. It’ll ask first for the originalPush.p12password, then have you set a new password for the new file. The new password is what your server side will use:
openssl pkcs12 -nocerts -out Pushkey.pem -in Push.p12
- Then concatenate the two
.pemfiles into one —Push.pem:
cat PushCert.pem PushKey.pem > Push.pem
The whole flow looks like this:

With the certificate ready, here’s the PHP push service. The code is simple — just be sure to use the Push.pem you just generated and the password you set during generation.
<?php
// DeviceToken (no spaces)
$deviceToken = '********************************************************';
// certificate password
$passphrase = '1111111111';
// notification body
$alert = 'This is a push notification!';
////////////////////////////////////////////////////////////////////////////////
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'Push.pem'); // the .pem we made above
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
// Open a connection to the APNS server
$fp = stream_socket_client(
'ssl://gateway.sandbox.push.apple.com:2195', // sandbox endpoint
$err,
$errstr,
60, // timeout
STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT,
$ctx);
if (!$fp)
exit("Failed to connect: $err $errstr" . PHP_EOL);
echo 'Connected to APNS' . PHP_EOL;
// build the dictionary
$body['aps'] = array(
'alert' => $alert,
'sound' => 'default',
'badge' => 66
);
// turn it into JSON
$payload = json_encode($body);
// assemble the binary message — format per Apple's docs, also outlined above:
// Command + Token length + deviceToken + Payload length + payload
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
// ship it to APNs
$result = fwrite($fp, $msg, strlen($msg));
if (!$result)
echo 'Fail to delivery Notification' . PHP_EOL;
else
echo 'Delivery Notification successfully' . PHP_EOL;
fclose($fp);
?>
Test run:

References
- https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction.html#//apple_ref/doc/uid/TP40008194-CH1-SW1
- https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/
- http://blog.csdn.net/shenjie12345678/article/details/41120637