iOS and Android Push Notifications with PushSharp

Push notifications are pretty tricky, especially interfacing with Apple’s push notification service. Below are some details I’ve figured out from a recent project.

Note: This article was specfically written about a 2.x version of PushSharp. While many of the concepts are probably the same, specific referenced APIs may be different or removed in more recent versions.

Certificate Installation

A few things about the SSL certificate for authenticating with Apple’s push notification service:

Export only the private key from the Mac’s key chain to a .p12 file PushSharp can have strange problems with the certificate file if it has more than just the private key in it

Install to Local Computer

  1. Win+R to run, then run mmc
  2. File -> Add Snap-In
  3. Pick Certificates
  4. Make it for Computer Account
  5. Local Computer
  6. Open the Certificates (Local Computer) node and drill down to Personal
  7. Right click on Personal and choose All Tasks -> Import
  8. Go through the wizard to import your p12 file(s).

Give IIS permission to read the certificate

  1. In Certificate manager, right click on the imported certificate
  2. All Tasks -> Manage Private Keys
  3. Under Security, click Add
  4. Enter IUSR and IIS_ISUSRS` and give them read permission

Bind certificates to ports

  1. In an elevated powershell command line, run Get-ChildItem -path cert:\LocalMachine\My
  2. This will list the thumbprints for the installed certificate(s)
  3. Copy the thumbprint of the certificate
  4. Fill in the thumbprint to the following script and run it. Keep the thumbprint around for later
bind-certs.ps1
1
2
3
4
5
6
$thumbprint="<THUMBPRINT>"

netsh http delete sslcert ipport=0.0.0.0:2195
netsh http delete sslcert ipport=0.0.0.0:2196
netsh http add sslcert ipport=0.0.0.0:2195 certhash="$thumbprint" appid='{472f53d0-29e1-4cf4-ba9c-79f362d8f6fa}'
netsh http add sslcert ipport=0.0.0.0:2196 certhash="$thumbprint" appid='{472f53d0-29e1-4cf4-ba9c-79f362d8f6fa}'

Using PushSharp

Make sure you’ve walked through all the above steps or you might get exceptions like “Client Authentication Error - Authentication failed because the remote party has closed the transport stream.”

Load the certificate

To instantiate an instnace of ApplePushService, you’ll need to load the certificate from the store.

LoadCertificate.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private X509Certificate2 FindApnsCert(string thumbprint)
{
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);

var cert = store.Certificates
.Cast<X509Certificate2>()
.SingleOrDefault(c => string.Equals(c.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase));

if (cert == null)
throw new Exception("No certificate with thumprint: " + thumbprint);

return cert;
}

Register Apple and Android services with PushSharp

register.cs
1
2
3
4
5
6
7
8
9
10
11

var pushBroker = new PushBroker();

var gcmToken = ConfigurationManager.AppSettings["GcmAuthToken"];
pushBroker.RegisterService<GcmNotification>(new GcmPushService((new GcmPushChannelSettings(gcmToken))));

var thumbprint = ConfigurationManager.AppSettings["AppleThumbprint"];
var cert = FindApnsCert(thumbprint);
bool isProduction = cert.SubjectName.Name.Contains("Production");
pushBroker.RegisterService<AppleNotification>(new ApplePushService(new ApplePushChannelSettings(isProduction, cert)));

Send Notifications

Each platform provides a unique identifier for your user’s phone. This identifier is unique to your app. You’ll need your mobile app to send this token to your server for later processing.

Apple’s tokens look something like this: 763e417c356c684b09d905d7311857e324355c45813b16255022fa567035932c.

Android’s tokens look something like this: APA91bGuym4jPO6fC5Urw_ikkaA9lotGR9-Spr5g3ulqtzF75eMqXOIkl_Y-Glki6_SSuTeEITOS-HV32mrByx9lRg8xx2ycMwplMZyMTFq0dTpqbIGIi9ki3sWSaFkp_bHIO6SXSLs

A malformed or bad token will be reported by PushSharp.

send-notifications.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14

// apple
pushBroker.QueueNotification(new AppleNotification()
.ForDeviceToken(appleToken)
.WithAlert("Hello world"));

// android
pushBroker.QueueNotification(new GcmNotification()
.ForDeviceRegistrtionId(googleToken)
.WithData(new Dictionary<string, string>()
{
{ "message", "Hello World" },
{ "title", "Test" }
}));

Error Handling and Logging

PushSharp works asynchronously: you queue up events and they are processed by a background thread. To provide feedback on processed notifications, PushSharp uses .NET Events. You’ll want to register to each of the following events and handle it accordingly:

  • OnChannelException : Seems to occur when there’s an error in the underlying network stream. Probably not much you can do to recover except restart the whole service
  • OnServiceException : Similar to above
  • OnNotificationFailed : occurs for a variety of reasons in which there was a problem sending a particular notification. For example, the provided token was invalid or rejected by the service. You may want to requeue your notification or mark it as errored.
  • OnDeviceSubscriptionExpired : occurs (only on Apple I think) when a device token has been expired. This can happen if the user turns off push notifications or uninstalls the app. You’ll probably want to delete the offending token from your database and acquire a new one the next time the user logs into your app.
  • OnDeviceSubscriptionChanged : similarly, Apple may notify PushSharp that a device token has been changed. The event passes back the old token and the new token, which you can use to update the database.
  • OnNotificationSent : occurs when a notification was successful. Log or mark your database rows as sent.

Use a Custom Tag Object

Since PushSharp operates asynchronously and fires back events when a notification is processed, it allows you to attach any object to a Notification’s Tag property. You can stick any information on there you might want to help processing the events. Events that support this feature pass back an INotification parameter that will have a Tag property on it.

I’d recommend creating a custom NotificationTag object that at least had a string for the DeviceToken. This will aid in debugging because it will allow you to log which device tokens are failing. You can add other needed properties later, such as UserID or DeviceType.

You can add the tag to the Notification object using the WithTag method, just like we used WithAlert and WithData above.