Products Customers Pricing Docs Help Blog

Push Notification Guide

If you haven't installed the SDK yet, please head over to the Push QuickStart to get our SDK up and running.

Introduction

Push Notifications are a great way to keep your users engaged and informed about your app. You can reach your entire user base quickly and effectively. This guide will help you through the setup process and the general usage of Parse to send push notifications.

Setting Up Push

Tutorial_link

Follow the iOS push tutorial.

If you want to start using push, start by completing the iOS Push tutorial to learn how to configure your app. Come back to this guide afterwards to learn more about the push features offered by Parse.





Installations

Every Parse application installed on a device registered for push notifications has an associated Installation object. The Installation object is where you store all the data needed to target push notifications. For example, in a baseball app, you could store the teams a user is interested in to send updates about their performance. Saving the Installation object is also required for tracking push-related app open events.

In iOS, Installation objects are available through the PFInstallation class, a subclass of PFObject. It uses the same API for storing and retrieving data. To access the current Installation object from your iOS app, use the [PFInstallation currentInstallation] method. The first time you save a PFInstallation, Parse will add it to your Installation class, and it will be available for targeting push notifications as long as its deviceToken field is set.

First, make your app register for remote notifications by adding the following in your application:didFinishLaunchingWithOptions: method (if you haven't already):

UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert |
                                                UIUserNotificationTypeBadge |
                                                UIUserNotificationTypeSound);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes
                                                                         categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];
let userNotificationTypes = (UIUserNotificationType.Alert |
                             UIUserNotificationType.Badge |
                             UIUserNotificationType.Sound);

let settings = UIUserNotificationSettings(forTypes: userNotificationTypes, categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()

We will then update our PFInstallation with the deviceToken once the device is registered for push notifications:

- (void)application:(UIApplication *)application
        didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    // Store the deviceToken in the current Installation and save it to Parse.
    PFInstallation *currentInstallation = [PFInstallation currentInstallation];
    [currentInstallation setDeviceTokenFromData:deviceToken];
    [currentInstallation saveInBackground];
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
    let installation = PFInstallation.currentInstallation()
    installation.setDeviceTokenFromData(deviceToken)
    installation.saveInBackground()
}

While it is possible to modify a PFInstallation just like you would a PFObject, there are several special fields that help manage and target devices.

  • badge: The current value of the icon badge for iOS apps. Changing this value on the PFInstallation will update the badge value on the app icon. Changes should be saved to the server so that they will be used for future badge-increment push notifications.
  • channels: An array of the channels to which a device is currently subscribed.
  • timeZone: The current time zone where the target device is located. This value is synchronized every time an Installation object is saved from the device (readonly).
  • deviceType: The type of device, "ios", "android", "winrt", "winphone", or "dotnet"(readonly).
  • pushType: This field is reserved for directing Parse to the push delivery network to be used. If the device is registered to receive pushes via GCM, this field will be marked "gcm". If this device is not using GCM, and is using Parse's push notification service, it will be blank (readonly).
  • installationId: Unique Id for the device used by Parse (readonly).
  • deviceToken: The Apple generated token used for iOS devices (readonly).
  • channelUris: The Microsoft-generated push URIs for Windows devices (readonly).
  • appName: The display name of the client application to which this installation belongs (readonly).
  • appVersion: The version string of the client application to which this installation belongs (readonly).
  • parseVersion: The version of the Parse SDK which this installation uses (readonly).
  • appIdentifier: A unique identifier for this installation's client application. In iOS, this is the Bundle Identifier (readonly).

Sending Pushes

There are two ways to send push notifications using Parse: channels and advanced targeting. Channels offer a simple and easy to use model for sending pushes, while advanced targeting offers a more powerful and flexible model. Both are fully compatible with each other and will be covered in this section.

Sending notifications is often done from the Parse.com push console, the REST API or from Cloud Code. However, push notifications can also be triggered by the existing client SDKs. If you decide to send notifications from the client SDKs, you will need to set Client Push Enabled in the Push Notifications settings of your Parse app.

However, be sure you understand that enabling Client Push can lead to a security vulnerability in your app, as outlined on our blog. We recommend that you enable Client Push for testing purposes only, and move your push notification logic into Cloud Code when your app is ready to go into production.

Client_push_settings

You can view your past push notifications on the Parse.com push console for up to 30 days after creating your push. For pushes scheduled in the future, you can delete the push on the push console as long as no sends have happened yet. After you send the push, the push console shows push analytics graphs.

Using Channels

The simplest way to start sending notifications is using channels. This allows you to use a publisher-subscriber model for sending pushes. Devices start by subscribing to one or more channels, and notifications can later be sent to these subscribers. The channels subscribed to by a given Installation are stored in the channels field of the Installation object.

Subscribing to Channels

A channel is identified by a string that starts with a letter and consists of alphanumeric characters, underscores, and dashes. It doesn't need to be explicitly created before it can be used and each Installation can subscribe to any number of channels at a time.

Adding a channel subscription can be done using the addUniqueObject: method in PFObject. For example, in a baseball score app, we could do:

// When users indicate they are Giants fans, we subscribe them to that channel.
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation addUniqueObject:@"Giants" forKey:@"channels"];
[currentInstallation saveInBackground];
// When users indicate they are Giants fans, we subscribe them to that channel.
let currentInstallation = PFInstallation.currentInstallation()
currentInstallation.addUniqueObject("Giants", forKey: "channels")
currentInstallation.saveInBackground()

Once subscribed to the "Giants" channel, your Installation object should have an updated channels field.

Installation_channel

Unsubscribing from a channel is just as easy:

// When users indicate they are no longer Giants fans, we unsubscribe them.
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation removeObject:@"Giants" forKey:@"channels"];
[currentInstallation saveInBackground];
// When users indicate they are Giants fans, we subscribe them to that channel.
let currentInstallation = PFInstallation.currentInstallation()
currentInstallation.removeObject("Giants", forKey: "channels")
currentInstallation.saveInBackground()

The set of subscribed channels is cached in the currentInstallation object:

NSArray *subscribedChannels = [PFInstallation currentInstallation].channels;
let subscribedChannels = PFInstallation.currentInstallation().channels

If you plan on changing your channels from Cloud Code or the data browser, note that you'll need to call some form of fetch prior to this line in order to get the most recent channels.

Sending Pushes to Channels

In the iOS SDK, the following code can be used to alert all subscribers of the "Giants" channel that their favorite team just scored. This will display a notification center alert to iOS users and a system tray notification to Android users.

// Send a notification to all devices subscribed to the "Giants" channel.
PFPush *push = [[PFPush alloc] init];
[push setChannel:@"Giants"];
[push setMessage:@"The Giants just scored!"];
[push sendPushInBackground];
// Send a notification to all devices subscribed to the "Giants" channel.
let push = PFPush()
push.setChannel("Giants")
push.setMessage("The Giants just scored!")
push.sendPushInBackground()

If you want to target multiple channels with a single push notification, you can use an NSArray of channels.

NSArray *channels = [NSArray arrayWithObjects:@"Giants", @"Mets", nil];
PFPush *push = [[PFPush alloc] init];

// Be sure to use the plural 'setChannels'.
[push setChannels:channels];
[push setMessage:@"The Giants won against the Mets 2-3."];
[push sendPushInBackground];
let channels = [ "Giants", "Mets" ]
let push = PFPush()

// Be sure to use the plural 'setChannels'.
push.setChannels(channels)
push.setMessage("The Giants won against the Mets 2-3.")
push.sendPushInBackground()

Using Advanced Targeting

While channels are great for many applications, sometimes you need more precision when targeting the recipients of your pushes. Parse allows you to write a query for any subset of your Installation objects using the querying API and to send them a push.

Since PFInstallation is a subclass of PFObject, you can save any data you want and even create relationships between Installation objects and your other objects. This allows you to send pushes to a very customized and dynamic segment of your user base.

Saving Installation Data

Storing data on an Installation object is just as easy as storing any other data on Parse. In our Baseball app, we could allow users to get pushes about game results, scores and injury reports.

// Store app language and version
PFInstallation *installation = [PFInstallation currentInstallation];
[installation setObject:@YES forKey:@"scores"];
[installation setObject:@YES forKey:@"gameResults"];
[installation setObject:@YES forKey:@"injuryReports"];
[installation saveInBackground];
// Store app language and version
let installation = PFInstallation.currentInstallation()
installation["scores"] = true
installation["gameResults"] = true
installation["injuryReports"] = true
installation.saveInBackground()

You can even create relationships between your Installation objects and other classes saved on Parse. To associate a PFInstallation with a particular user, for example, you can simply store the current user on the PFInstallation.

// Associate the device with a user
PFInstallation *installation = [PFInstallation currentInstallation];
installation[@"user"] = [PFUser currentUser];
[installation saveInBackground];
// Associate the device with a user
let installation = PFInstallation.currentInstallation()
installation["user"] = PFUser.currentUser()
installation.saveInBackground()

Sending Pushes to Queries

Once you have your data stored on your Installation objects, you can use a PFQuery to target a subset of these devices. Installation queries work just like any other Parse query, but we use the special static method [PFInstallation query] to create it. We set this query on our PFPush object, before sending the notification.

// Create our Installation query
PFQuery *pushQuery = [PFInstallation query];
[pushQuery whereKey:@"injuryReports" equalTo:YES];

// Send push notification to query
PFPush *push = [[PFPush alloc] init];
[push setQuery:pushQuery]; // Set our Installation query
[push setMessage:@"Willie Hayes injured by own pop fly."];
[push sendPushInBackground];
// Create our Installation query
let pushQuery = PFInstallation.query()
pushQuery.whereKey("injuryReports", equalTo: true)

// Send push notification to query
let push = PFPush()
push.setQuery(pushQuery) // Set our Installation query
push.setMessage("Willie Hayes injured by own pop fly.")
push.sendPushInBackground()

We can even use channels with our query. To send a push to all subscribers of the "Giants" channel but filtered by those who want score update, we can do the following:

// Create our Installation query
PFQuery *pushQuery = [PFInstallation query];
[pushQuery whereKey:@"channels" equalTo:@"Giants"]; // Set channel
[pushQuery whereKey:@"scores" equalTo:YES];

// Send push notification to query
PFPush *push = [[PFPush alloc] init];
[push setQuery:pushQuery];
[push setMessage:@"Giants scored against the A's! It's now 2-2."];
[push sendPushInBackground];
// Create our Installation query
let pushQuery = PFInstallation.query()
pushQuery.whereKey("channels", equalTo: "Giants") // Set channel
pushQuery.whereKey("scores", equalTo: true)

// Send push notification to query
let push = PFPush()
push.setQuery(pushQuery) // Set our Installation query
push.setMessage("Giants scored against the A's! It's now 2-2.")
push.sendPushInBackground()

If we store relationships to other objects in our Installation class, we can also use those in our query. For example, we could send a push notification to all users near a given location like this.

// Find users near a given location
PFQuery *userQuery = [PFUser query];
[userQuery whereKey:@"location"
       nearGeoPoint:stadiumLocation
        withinMiles:@1]

// Find devices associated with these users
PFQuery *pushQuery = [PFInstallation query];
[pushQuery whereKey:@"user" matchesQuery:userQuery];

// Send push notification to query
PFPush *push = [[PFPush alloc] init];
[push setQuery:pushQuery]; // Set our Installation query
[push setMessage:@"Free hotdogs at the Parse concession stand!"];
[push sendPushInBackground];
// Find users near a given location
let userQuery = PFUser.query()
userQuery.whereKey("location", nearGeoPoint: stadiumLocation, withinMiles: 1)

// Find devices associated with these users
let pushQuery = PFInstallation.query()
pushQuery.whereKey("user", matchesQuery: userQuery)

// Send push notification to query
let push = PFPush()
push.setQuery(pushQuery) // Set our Installation query
push.setMessage("Free hotdogs at the Parse concession stand!")
push.sendPushInBackground()

Sending Options

Push notifications can do more than just send a message. In iOS, pushes can also include the sound to be played, the badge number to display as well as any custom data you wish to send. An expiration date can also be set for the notification in case it is time sensitive.

Customizing your Notifications

If you want to send more than just a message, you will need to use an NSDictionary to package all of the data. There are some reserved fields that have a special meaning.

  • alert: the notification's message.
  • badge: (iOS only) the value indicated in the top right corner of the app icon. This can be set to a value or to Increment in order to increment the current value by 1.
  • sound: (iOS only) the name of a sound file in the application bundle.
  • content-available: (iOS only) If you are a writing a Newsstand app, or an app using the Remote Notification Background Mode introduced in iOS7 (a.k.a. "Background Push"), set this value to 1 to trigger a background download.
  • category: (iOS only) the identifier of the UIUserNotificationCategory for this push notification.
  • uri: (Android only) an optional field that contains a URI. When the notification is opened, an Activity associated with opening the URI is launched.
  • title: (Android, Windows 8, and Windows Phone 8 only) the value displayed in the Android system tray or Windows toast notification.

For example, to send a notification that increases the current badge number by 1 and plays a custom sound, you can do the following:

NSDictionary *data = @{
  @"alert" : @"The Mets scored! The game is now tied 1-1!",
  @"badge" : @"Increment",
  @"sounds" : @"cheering.caf"
};
PFPush *push = [[PFPush alloc] init];
[push setChannels:@[ @"Mets" ]];
[push setData:data];
[push sendPushInBackground];
let data = [
  "alert" : "The Mets scored! The game is now tied 1-1!",
  "badge" : "Increment",
  "sounds" : "cheering.caf"
]
let push = PFPush()
push.setChannels(["Mets"])
push.setData(data)
push.sendPushInBackground()

It is also possible to specify your own data in this dictionary. As we'll see in the Receiving Notifications section, you will have access to this data only when the user opens your app via the notification. This can be useful for displaying a different view controller when a user opens certain notifications.

NSDictionary *data = @{
  @"alert" : @"Ricky Vaughn was injured in last night's game!",
  @"name" : @"Vaughn",
  @"newsItem" : @"Man bites dog"
};
PFPush *push = [[PFPush alloc] init];
[push setQuery:injuryReportsQuery];
[push setChannel:@"Indians"];
[push setData:data];
[push sendPushInBackground];
let data = [
  "alert" : "Ricky Vaughn was injured in last night's game!",
  "name" : "Vaughn",
  "newsItem" : "Man bites dog"
]
let push = PFPush()
push.setQuery(injuryReportsdata)
push.setChannel("Indians")
push.setData(data)
push.sendPushInBackground()

Whether your push notifications increment the app's badge or set it to a specific value, your app will eventually need to clear its badge. This is covered in Clearing the Badge.

Setting an Expiration Date

When a user's device is turned off or not connected to the internet, push notifications cannot be delivered. If you have a time sensitive notification that is not worth delivering late, you can set an expiration date. This avoids needlessly alerting users of information that may no longer be relevant.

There are two methods provided by the PFPush class to allow setting an expiration date for your notification. The first is expireAtDate: which simply takes an NSDate specifying when Parse should stop trying to send the notification.

// Create date object for tomorrow
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setYear:2015];
[comps setMonth:4];
[comps setDay:27];
NSCalendar *gregorian =
  [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *date = [gregorian dateFromComponents:comps];

// Send push notification with expiration date
PFPush *push = [[PFPush alloc] init];
[push expireAtDate:date];
[push setQuery:everyoneQuery];
[push setMessage:@"Season tickets on sale until April 27th"];
[push sendPushInBackground];

There is however a caveat with this method. Since device clocks are not guaranteed to be accurate, you may end up with inaccurate results. For this reason, the PFPush class also provides the expireAfterTimeInterval: method which accepts an NSTimeInterval object. The notification will expire after the specified interval has elapsed.

// Create time interval
NSTimeInterval interval = 60*60*24*7; // 1 week

// Send push notification with expiration interval
PFPush *push = [[PFPush alloc] init];
[push expireAfterTimeInterval:interval];
[push setQuery:everyoneQuery];
[push setMessage:@"Season tickets on sale until May 3rd"];
[push sendPushInBackground];

Targeting by Platform

If you build a cross platform app, it is possible you may only want to target devices of a particular operating system. Advanced Targeting allow you to filter which of these devices are targeted.

The following example would send a different notification to Android, iOS, and Windows users.

PFQuery *query = [PFInstallation query];
[query whereKey:@"channels" equalTo:@"suitcaseOwners"];

// Notification for Android users
[query whereKey:@"deviceType" equalTo:@"android"];
PFPush *androidPush = [[PFPush alloc] init];
[androidPush setMessage:@"Your suitcase has been filled with tiny robots!"];
[androidPush setQuery:query];
[androidPush sendPushInBackground];

// Notification for iOS users
[query whereKey:@"deviceType" equalTo:@"ios"];
PFPush *iOSPush = [[PFPush alloc] init];
[iOSPush setMessage:@"Your suitcase has been filled with tiny apples!"];
[iOSPush setChannel:@"suitcaseOwners"];
[iOSPush setQuery:query];
[iOSPush sendPushInBackground];

// Notification for Windows 8 users
[query whereKey:@"deviceType" equalTo:@"winrt"];
PFPush *winPush = [[PFPush alloc] init];
[winPush setMessage:@"Your suitcase has been filled with tiny glass!"];
[winPush setQuery:query];
[winPush sendPushInBackground];

// Notification for Windows Phone 8 users
[query whereKey:@"deviceType" equalTo:@"winphone"];
PFPush *winPush = [[PFPush alloc] init];
[wpPush setMessage:@"Your suitcase is very hip; very metro."];
[wpPush setQuery:query];
[wpPush sendPushInBackground];

Scheduling Pushes

Sending scheduled push notifications is not currently supported by the iOS SDK. Take a look at the REST API, JavaScript SDK or the Parse.com push console.

Receiving Pushes

As we saw in the Customizing Your Notification section, it is possible to send arbitrary data along with your notification message. We can use this data to modify the behavior of your app when a user opens a notification. For example, upon opening a notification saying that a friend commented on a user's picture, it would be nice to display this picture.

Due to the package size restrictions imposed by Apple, you need to be careful in managing the amount of extra data sent, since it will cut down on the maximum size of your message. For this reason, it is recommended that you keep your extra keys and values as small as possible.

NSDictionary *data = @{
  @"alert" : @"James commented on your photo!",
  @"p" : @"vmRZXZ1Dvo" // Photo's object id
};
PFPush *push = [[PFPush alloc] init];
[push setQuery:photoOwnerQuery];
[push setData:data];
[push sendPushInBackground];
let data = [
  "alert" : "James commented on your photo!",
  "p" : "vmRZXZ1Dvo" // Photo's object id
]
let push = PFPush()
push.setQuery(photoOwnerQuery)
push.setData(data)
push.sendPushInBackground()

Responding to the Payload

When an app is opened from a notification, the data is made available in the application:didFinishLaunchingWithOptions: methods through the launchOptions dictionary.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  . . .
  // Extract the notification data
  NSDictionary *notificationPayload = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];

  // Create a pointer to the Photo object
  NSString *photoId = [notificationPayload objectForKey:@"p"];
  PFObject *targetPhoto = [PFObject objectWithoutDataWithClassName:@"Photo"
                                                          objectId:photoId];

  // Fetch photo object
  [targetPhoto fetchIfNeededInBackgroundWithBlock:^(PFObject *object, NSError *error) {
    // Show photo view controller
    if (!error && [PFUser currentUser]) {
      PhotoVC *viewController = [[PhotoVC alloc] initWithPhoto:object];
      [self.navController pushViewController:viewController animated:YES];
    }
  }];
}

If your app is already running when the notification is received, the data is made available in the application:didReceiveRemoteNotification:fetchCompletionHandler: method through the userInfo dictionary.

- (void)application:(UIApplication *)application
  didReceiveRemoteNotification:(NSDictionary *)userInfo
        fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))handler {
  // Create empty photo object
  NSString *photoId = [userInfo objectForKey:@"p"];
  PFObject *targetPhoto = [PFObject objectWithoutDataWithClassName:@"Photo"
                                                          objectId:photoId];

  // Fetch photo object
  [targetPhoto fetchIfNeededInBackgroundWithBlock:^(PFObject *object, NSError *error) {
    // Show photo view controller
    if (error) {
      handler(UIBackgroundFetchResultFailed);
    } else if ([PFUser currentUser]) {
      PhotoVC *viewController = [[PhotoVC alloc] initWithPhoto:object];
      [self.navController pushViewController:viewController animated:YES];
      handler(UIBackgroundFetchResultNewData);
    } else {
      handler(UIBackgroundModeNoData);
    }
  }];
}
func application(application: UIApplication,
                 didReceiveRemoteNotification userInfo: [NSObject : AnyObject],
                 fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
  if let photoId: String = userInfo["p"] as? String {
    let targetPhoto = PFObject(withoutDataWithClassName: "Photo", objectId: photoId)
    targetPhoto.fetchIfNeededInBackgroundWithBlock({ (object: PFObject!, error: NSError!) -> Void in
      // Show photo view controller
      if error != nil {
        completionHandler(UIBackgroundFetchResult.Failed)
      } else if PFUser.currentUser() != nil {
        let viewController = PhotoVC(withPhoto: object)
        self.navController.pushViewController(viewController, animated: true)
        completionHandler(UIBackgroundFetchResult.NewData)
      } else {
        completionHandler(UIBackgroundFetchResult.NoData)
      }
    })
  }
  handler(UIBackgroundFetchResult.NoData)
}

You can read more about handling push notifications in Apple's Local and Push Notification Programming Guide.

Tracking Pushes and App Opens

To track your users' engagement over time and the effect of push notifications, we provide some hooks in the PFAnalytics class. You can view the open rate for a specific push notification on the Parse.com push console. You can also view overall app open and push open graphs are on the Parse analytics console. Our analytics graphs are rendered in real time, so you can easily verify that your application is sending the correct analytics events before your next release.

This section assumes that you've already set up your application to save the Installation object. Push open tracking only works when your application's devices are associated with saved Installation objects.

First, add the following to your application:didFinishLaunchingWithOptions: method to collect information about when your application was launched, and what triggered it. The extra checks ensure that, even with iOS 7's more advanced background push features, a single logical app-open or push-open event is counted as such.

if (application.applicationState != UIApplicationStateBackground) {
  // Track an app open here if we launch with a push, unless
  // "content_available" was used to trigger a background push (introduced
  // in iOS 7). In that case, we skip tracking here to avoid double
  // counting the app-open.
  BOOL preBackgroundPush = ![application respondsToSelector:@selector(backgroundRefreshStatus)];
  BOOL oldPushHandlerOnly = ![self respondsToSelector:@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)];
  BOOL noPushPayload = ![launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
  if (preBackgroundPush || oldPushHandlerOnly || noPushPayload) {
    [PFAnalytics trackAppOpenedWithLaunchOptions:launchOptions];
  }
}
if application.applicationState != UIApplicationState.Background {
  // Track an app open here if we launch with a push, unless
  // "content_available" was used to trigger a background push (introduced
  // in iOS 7). In that case, we skip tracking here to avoid double
  // counting the app-open.
  let oldPushHandlerOnly = !self.respondsToSelector(Selector("application:didReceiveRemoteNotification:fetchCompletionHandler:"))
  let noPushPayload: AnyObject? = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey]?
  if oldPushHandlerOnly || noPushPayload != nil {
    PFAnalytics.trackAppOpenedWithLaunchOptions(launchOptions)
  }
}

Second, if your application is running or backgrounded, the application:didReceiveRemoteNotification: method handles the push payload instead. If the user acts on a push notification while the application is backgrounded, the application will be brought to the foreground. To track this transition as the application being "opened from a push notification," perform one more check before calling any tracking code:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
  if (application.applicationState == UIApplicationStateInactive) {
    // The application was just brought from the background to the foreground,
    // so we consider the app as having been "opened by a push notification."
    [PFAnalytics trackAppOpenedWithRemoteNotificationPayload:userInfo];
  }
}

Finally, if using iOS 7 any of its new push features (including the new "content-available" push functionality), be sure to also implement the iOS 7-only handler:

- (void)application:(UIApplication *)application
        didReceiveRemoteNotification:(NSDictionary *)userInfo
        fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  if (application.applicationState == UIApplicationStateInactive) {
    [PFAnalytics trackAppOpenedWithRemoteNotificationPayload:userInfo];
  }
}
func application(application: UIApplication,
                 didReceiveRemoteNotification userInfo: [NSObject : AnyObject],
                 fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
  if application.applicationState == UIApplicationState.Inactive {
    PFAnalytics.trackAppOpenedWithRemoteNotificationPayload(userInfo)
  }
}

Tracking on OS X

If your OS X application supports receiving push notifications and you'd like to track application opens related to pushes, add hooks to the application:didReceiveRemoteNotification: method (as in iOS) and the following to applicationDidFinishLaunching:

- (void)applicationDidFinishLaunching:(NSNotification *)notification {
  // ... other Parse setup logic here
  [PFAnalytics trackAppOpenedWithRemoteNotificationPayload:[notification userInfo]];
}
func applicationDidFinishLaunching(notification: NSNotification) {
  // ... other Parse setup logic here
  PFAnalytics.trackAppOpenedWithRemoteNotificationPayload(notification.userInfo)
}

Tracking Local Notifications (iOS only)

To track analytics around local notifications, note that application:didReceiveLocalNotification: is called in addition to application:didFinishLaunchingWithOptions:, if implemented. Please be careful to prevent tracking duplicate events.

Clearing the Badge

A good time to clear your app's badge is usually when your app is opened. Setting the badge property on the current installation will update the application icon badge number and ensure that the latest badge value will be persisted to the server on the next save. All you need to do is:

- (void)applicationDidBecomeActive:(UIApplication *)application {
  PFInstallation *currentInstallation = [PFInstallation currentInstallation];
  if (currentInstallation.badge != 0) {
    currentInstallation.badge = 0;
    [currentInstallation saveEventually];
  }
  // ...
}
func applicationDidBecomeActive(application: UIApplication) {
  let currentInstallation = PFInstallation.currentInstallation()
  if currentInstallation.badge != 0 {
    currentInstallation.badge = 0
    currentInstallation.saveEventually()
  }
  // ...
}

The UIApplicationDelegate documentation contains more information on hooks into an app’s life cycle; the ones which are most relevant for resetting the badge count are applicationDidBecomeActive:, application:didFinishLaunchingWithOptions:, and application:didReceiveRemoteNotification:.

Push Experiments

You can A/B test your push notifications to figure out the best way to keep your users engaged. With A/B testing, you can simultaneously send two versions of your push notification to different devices, and use each version's push open rates to figure out which one is better. You can test by either message or send time.

A/B Testing

Our web push console guides you through every step of setting up an A/B test:

  1. For each push campaign sent through the Parse web push console, you can allocate a subset of your devices to be in the experiment's test audience, which Parse will automatically split into two equally-sized experiment groups. For each experiment group, you can specify a different push message. The remaining devices will be saved so that you can send the winning message to them later. Parse will randomly assign devices to each group to minimize the chance for a test to affect another test's results (although we still don't recommend running multiple A/B tests over the same devices on the same day).
  2. Experiment_enable Experiment_messages


  3. After you send the push, you can come back to the push console to see in real time which version resulted in more push opens, along with other metrics such as statistical confidence interval. It's normal for the number of recipients in each group to be slightly different because some devices that we had originally allocated to that experiment group may have uninstalled the app. It's also possible for the random group assignment to be slightly uneven when the test audience size is small. Since we calculate open rate separately for each group based on recipient count, this should not significantly affect your experiment results.
  4. Experiment_results


  5. If you are happy with the way one message performed, you can send that to the rest of your app's devices (i.e. the “Launch Group”). This step only applies to A/B tests where you vary the message.
  6. Experiment_launch


Push experiments are supported on all recent Parse SDKs (iOS v1.2.13+, Android v1.4.0+, .NET v1.2.7+). Before running experiments, you must instrument your app with push open tracking.

Experiment Statistics

Parse provides guidance on how to run experiments to achieve statistically significant results.

Test Audience Size

When you setup a push message experiment, we'll recommend the minimum size of your test audience. These recommendations are generated through simulations based on your app's historical push open rates. For big push campaigns (e.g. 100k+ devices), this recommendation is usually small subset of your devices. For smaller campaigns (e.g. < 5k devices), this recommendation is usually all devices. Using all devices for your test audience will not leave any remaining devices for the launch group, but you can still gain valuable insight into what type of messaging works better so you can implement similar messaging in your next push campaign.

Confidence Interval

After you send your pushes to experiment groups, we'll also provide a statistical confidence interval when your experiment has collected enough data to have statistically significant results. This confidence interval is in absolute percentage points of push open rate (e.g. if the open rates for groups A and B are 3% and 5%, then the difference is reported as 2 percentage points). This confidence interval is a measure of how much difference you would expect to see between the two groups if you repeat the same experiment many times.

Just after a push send, when only a small number of users have opened their push notifications, the open rate difference you see between groups A and B could be due to random chance, so it might not be reproducible if you run the same experiment again. After your experiment collects more data over time, we become increasingly confident that the observed difference is a true difference. As this happens, the confidence interval will become narrower, allowing us to more accurately estimate the true difference between groups A and B. Therefore, we recommend that you wait until there is enough data to generate a statistical confidence interval before deciding which group's push is better.

Troubleshooting

Setting up Push Notifications is often a source of frustration for developers. The process is complicated and invites problems to happen along the way. If you run into issues, try some of these troubleshooting tips.

  • Make sure you are using the correct Bundle Identifier in the Info.plist file (as described in step 4.1 of the iOS Push Notifications tutorial, titled, "Configuring a Push Enabled iOS Application."
  • Make sure you set the correct provisioning profile in Project > Build Settings (as described in step 4.3 of the iOS Push Notifications tutorial.
  • Clean your project and restart Xcode.
  • Try regenerating the provisioning profile by navigating to Certificates, Identifiers & Profiles, changing the App ID set on the provisioning profile, and changing it back. You will need to reinstall the profile as described in step two of the tutorial (Creating the Provisioning Profile) and set it in your Project's Build Settings as described in step 4 ( Configuring a Push Enabled iOS Application).
  • Open the Xcode Organizer and delete all expired and unused provisioning profiles from both your computer and your iOS device.
  • If everything compiles and runs with no errors, but you are still not receiving pushes, make sure that your app has been given permission to receive notifications. You can verify this in your iOS device's Settings > Notification > YourAppName.
  • If your app has been granted permission to receive push notifications, make sure that you are code signing your app with the correct provisioning profile. If you have uploaded a Development Push Notification Certificate to Parse, you will only receive push notifications if you built your app with a Development Provisioning Profile. If you have uploaded a Production Push Notification Certificate, you should sign your app with a Distribution Provisioning Profile. Ad Hoc and App Store Distribution Provisioning Profiles should both work when your app is configured with a Production Push Notification Certificate.
  • When enabling push notifications for an existing App ID in the Apple iOS Provisioning Portal, make sure to regenerate the provisioning profile, then add the updated profile to the Xcode Organizer.
  • Distribution push notifications need to be enabled prior to submitting an app to the App Store. Make sure you have followed Section 7, Preparing for the App Store, prior to submitting your app. If you skipped any of these steps, you might need to submit a new binary to the App Store.
  • Double check that your app can receive distribution push notifications when signed with an Ad Hoc profile. This configuration is the closest you can get to an App Store provisioned app.
  • Check the number of recipients in your Parse.com push console. Does it match the expected number of recipients? Your push might be targeted incorrectly.
  • If your app has been released for a while, it's possible for the recipient estimate on the push composer page to be higher than the pushes sent value on the push results page. The push composer estimate is generated via running your push segment query over your app's installation table. We do not automatically delete installation objects when the users uninstall your app. When we try to send a push, we detect uninstalled installations and do not include them in the pushes sent value on the results page.

Push Notification Guide

If you haven't installed the SDK yet, please head over to the Push QuickStart to get our SDK up and running.

Introduction

Push notifications are a great way to keep your users engaged and informed about your app. You can reach your entire user base quickly and effectively. This guide will help you through the setup process and the general usage of Parse to send push notifications.

Setting Up Push

Tutorial_link

Follow the Android push tutorial.

If you want to start using push, start by completing the Android Push tutorial to learn how to configure your app. Come back to this guide afterwards to learn more about the push features offered by Parse.

The Parse library provides push notifications using Google Cloud Messaging (GCM) if possible. On devices that do not support GCM (such as Amazon Kindle Fire), Parse will use a background service that maintains a persistent connection to the Parse cloud to deliver pushes. This allows Parse Push to work on all devices running Android 2.3 or higher.

When sending pushes to Android devices with GCM, there are several pieces of information that Parse keeps track of automatically:

  • Registration ID: The GCM registration ID uniquely identifies an app/device pairing for push purposes.
  • Sender ID: The GCM sender ID is a public number that identifies the sender of a push notification.
  • API key: The GCM API key is a server secret that allows a server to send pushes to a registration ID on behalf of a particular sender ID.

The Parse Android SDK chooses a reasonable default configuration so that you do not have to worry about GCM registration ids, sender ids, or API keys. In particular, the SDK will automatically register your app for push at startup time using Parse's sender ID (1076345567071) and will store the resulting registration ID in the deviceToken field of the app's current ParseInstallation.

However, as an advanced feature for developers that want to send pushes from multiple push providers, Parse allows you to optionally register your app for pushes with additional GCM sender IDs. To do this, specify the additional GCM sender ID with the following <meta-data> tag as a child of the <application> element in your app's AndroidManifest.xml:

<meta-data android:name="com.parse.push.gcm_sender_id"
           android:value="id:YOUR_SENDER_ID" />;

In the sample snippet above, YOUR_SENDER_ID should be replaced by a numeric GCM sender ID. Note that the Parse SDK expects you to prefix your sender ID with an id: prefix, as shown in the sample snippet.

If you want to register your app with multiple additional sender IDs, then the android:value in the <meta-data> element above should hold a comma-delimited list of sender IDs, as in the following snippet:

<meta-data android:name="com.parse.push.gcm_sender_id"
           android:value="id:YOUR_SENDER_ID_1,YOUR_SENDER_ID_2,YOUR_SENDER_ID_3" />;

Installations

Every Parse application installed on a device registered for push notifications has an associated Installation object. The Installation object is where you store all the data needed to target push notifications. For example, in a baseball app, you could store the teams a user is interested in to send updates about their performance. Saving the Installation object is also required for tracking push-related app open events.

In Android, Installation objects are available through the ParseInstallation class, a subclass of ParseObject. It uses the same API for storing and retrieving data. To access the current Installation object from your Android app, use the ParseInstallation.getCurrentInstallation() method. The first time you save a ParseInstallation, Parse will add it to your Installation class and it will be available for targeting push notifications.

// Save the current Installation to Parse.
ParseInstallation.getCurrentInstallation().saveInBackground();

While it is possible to modify a ParseInstallation just like you would a ParseObject, there are several special fields that help manage and target devices.

  • badge: The current value of the icon badge for iOS apps. Changes to this value on the server will be used for future badge-increment push notifications.
  • channels: An array of the channels to which a device is currently subscribed.
  • timeZone: The current time zone where the target device is located. This value is synchronized every time an Installation object is saved from the device (readonly).
  • deviceType: The type of device, "ios" or "android" (readonly).
  • pushType: This field is reserved for directing Parse to the push delivery network to be used. If the device is registered to receive pushes via GCM, this field will be marked "gcm". If this device is not using GCM, and is using Parse's push notification service, it will be blank (readonly).
  • GCMSenderId: This field only has meaning for Android installations that use the GCM push type. It is reserved for directing Parse to send pushes to this installation with an alternate GCM sender ID. This field should generally not be set unless you are uploading installation data from another push provider. If you set this field, then you must set the GCM API key corresponding to this GCM sender ID in your Parse application's push settings.
  • installationId: Unique Id for the device used by Parse (readonly).
  • deviceToken: The Apple generated token used for iOS devices, or the token used by GCM to keep track of registration ID (readonly).
  • channelUris: The Microsoft-generated push URIs for Windows devices (readonly).
  • appName: The display name of the client application to which this installation belongs (readonly).
  • appVersion: The version string of the client application to which this installation belongs (readonly).
  • parseVersion: The version of the Parse SDK which this installation uses (readonly).
  • appIdentifier: A unique identifier for this installation's client application. This parameter is not supported in Android.(readonly).

Sending Pushes

There are two ways to send push notifications using Parse: channels and advanced targeting. Channels offer a simple and easy to use model for sending pushes, while advanced targeting offers a more powerful and flexible model. Both are fully compatible with each other and will be covered in this section.

Sending notifications is often done from the Parse.com push console, the REST API or from Cloud Code. However, push notifications can also be triggered by the existing client SDKs. If you decide to send notifications from the client SDKs, you will need to set Client Push Enabled in the Push Notifications settings of your Parse app.

However, be sure you understand that enabling Client Push can lead to a security vulnerability in your app, as outlined on our blog. We recommend that you enable Client Push for testing purposes only, and move your push notification logic into Cloud Code when your app is ready to go into production.

Client_push_settings

You can view your past push notifications on the Parse.com push console for up to 30 days after creating your push. For pushes scheduled in the future, you can delete the push on the push console as long as no sends have happened yet. After you send the push, the push console shows push analytics graphs.

Using Channels

The simplest way to start sending notifications is using channels. This allows you to use a publisher-subscriber model for sending pushes. Devices start by subscribing to one or more channels, and notifications can later be sent to these subscribers. The channels subscribed to by a given Installation are stored in the channels field of the Installation object.

Subscribing to Channels

A channel is identified by a string that starts with a letter and consists of alphanumeric characters, underscores, and dashes. It doesn't need to be explicitly created before it can be used and each Installation can subscribe to any number of channels at a time.

Subscribing to a channel can be done using a single method call. For example, in a baseball score app, we could do:

// When users indicate they are Giants fans, we subscribe them to that channel.
ParsePush.subscribeInBackground("Giants");

By default, the main activity for your app will be run when a user responds to notifications.

Once subscribed to the "Giants" channel, your Installation object should have an updated channels field.

Installation_channel

Unsubscribing from a channel is just as easy:

// When users indicate they are no longer Giants fans, we unsubscribe them.
ParsePush.unsubscribeInBackground("Giants");

You can also get the set of channels that the current device is subscribed to using:

List<String> subscribedChannels = ParseInstallation.getCurrentInstallation().getList("channels");

Neither the subscribe method nor the unsubscribe method blocks the thread it is called from. The subscription information is cached on the device's disk if the network is inaccessible and transmitted to the Parse Cloud as soon as the network is usable. This means you don't have to worry about threading or callbacks while managing subscriptions.

Sending Pushes to Channels

In the Android SDK, the following code can be used to alert all subscribers of the "Giants" channel that their favorite team just scored. This will display a notification center alert to iOS users and a system tray notification to Android users.

ParsePush push = new ParsePush();
push.setChannel("Giants");
push.setMessage("The Giants just scored! It's now 2-2 against the Mets.");
push.sendInBackground();

If you want to target multiple channels with a single push notification, you can use a LinkedList of channels.

LinkedList<String> channels = new LinkedList<String>();
channels.add("Giants");
channels.add("Mets");

ParsePush push = new ParsePush();
push.setChannels(channels); // Notice we use setChannels not setChannel
push.setMessage("The Giants won against the Mets 2-3.");
push.sendInBackground();

Using Advanced Targeting

While channels are great for many applications, sometimes you need more precision when targeting the recipients of your pushes. Parse allows you to write a query for any subset of your Installation objects using the querying API and to send them a push.

Since ParseInstallation is a subclass of ParseObject, you can save any data you want and even create relationships between Installation objects and your other objects. This allows you to send pushes to a very customized and dynamic segment of your user base.

Saving Installation Data

Storing data on an Installation object is just as easy as storing any other data on Parse. In our Baseball app, we could allow users to get pushes about game results, scores and injury reports.

// Store app language and version
ParseInstallation installation = ParseInstallation.getCurrentInstallation();
installation.put("scores",true);
installation.put("gameResults",true);
installation.put("injuryReports",true);
installation.saveInBackground();

You can even create relationships between your Installation objects and other classes saved on Parse. To associate an Installation with a particular user, for example, you can simply store the current user on the ParseInstallation.

// Associate the device with a user
ParseInstallation installation = ParseInstallation.getCurrentInstallation();
installation.put("user",ParseUser.getCurrentUser());
installation.saveInBackground();

Sending Pushes to Queries

Once you have your data stored on your Installation objects, you can use a PFQuery to target a subset of these devices. Installation queries work just like any other Parse query, but we use the special static method ParseInstallation.getQuery() to create it. We set this query on our PFPush object, before sending the notification.

// Create our Installation query
ParseQuery pushQuery = ParseInstallation.getQuery();
pushQuery.whereEqualTo("injuryReports", true);

// Send push notification to query
ParsePush push = new ParsePush();
push.setQuery(pushQuery); // Set our Installation query
push.setMessage("Willie Hayes injured by own pop fly.");
push.sendInBackground();

We can even use channels with our query. To send a push to all subscribers of the "Giants" channel but filtered by those who want score update, we can do the following:

// Create our Installation query
ParseQuery pushQuery = ParseInstallation.getQuery();
pushQuery.whereEqualTo("channels", "Giants"); // Set the channel
pushQuery.whereEqualTo("scores", true);

// Send push notification to query
ParsePush push = new ParsePush();
push.setQuery(pushQuery);
push.setMessage("Giants scored against the A's! It's now 2-2.");
push.sendInBackground();

If we store relationships to other objects in our Installation class, we can also use those in our query. For example, we could send a push notification to all users near a given location like this.

// Find users near a given location
ParseQuery userQuery = ParseUser.getQuery();
userQuery.whereWithinMiles("location", stadiumLocation, 1.0)

// Find devices associated with these users
ParseQuery pushQuery = ParseInstallation.getQuery();
pushQuery.whereMatchesQuery("user", userQuery);

// Send push notification to query
ParsePush push = new ParsePush();
push.setQuery(pushQuery); // Set our Installation query
push.setMessage("Free hotdogs at the Parse concession stand!");
push.sendInBackground();

Sending Options

Push notifications can do more than just send a message. In Android, pushes can also include custom data you wish to send. You have complete control of how you handle the data included in your push notification as we will see in the Receiving Notifications section. An expiration date can also be set for the notification in case it is time sensitive.

Customizing your Notifications

If you want to send more than just a message, you will need to use a JSONObject to package all of the data. There are some reserved fields that have a special meaning in Android.

  • alert: the notification's message.
  • badge: (iOS only) the value indicated in the top right corner of the app icon. This can be set to a value or to Increment in order to increment the current value by 1.
  • sound: (iOS only) the name of a sound file in the application bundle.
  • content-available: (iOS only) If you are a writing a Newsstand app, or an app using the Remote Notification Background Mode introduced in iOS7 (a.k.a. "Background Push"), set this value to 1 to trigger a background download.
  • category: (iOS only) the identifier of the UIUserNotificationCategory for this push notification.
  • uri: (Android only) an optional field that contains a URI. When the notification is opened, an Activity associated with opening the URI is launched.
  • title: (Android, Windows 8, & Windows Phone 8 only) the value displayed in the Android system tray or Windows 8 toast notification.

For example, to send a notification that would increases the badge number by 1 and plays a custom sound, you can do the following. Note that you can set these properties from your Android client, but they would only take effect in the iOS version of your app. The badge and sound fields would have no effects for Android recipients.

JSONObject data = new JSONObject("{\"alert\": \"The Mets scored!\",
                                   \"badge\": \"Increment\",
                                   \"sound\": \"cheering.caf\"}");

ParsePush push = new ParsePush();
push.setChannel("Mets");
push.setData(data);
push.sendPushInBackground();

It is also possible to specify your own data in this dictionary. As we'll see in the Receiving Notifications section, you're able to use the data sent with your push to do custom processing when a user receives and interacts with a notification.

JSONObject data = new JSONObject("{\"name\": \"Vaughn\",
                                   \"newsItem\": \"Man bites dog\"}"));

ParsePush push = new ParsePush();
push.setQuery(injuryReportsQuery);
push.setChannel("Indians");
push.setData(data);
push.sendPushInBackground();

Setting an Expiration Date

When a user's device is turned off or not connected to the internet, push notifications cannot be delivered. If you have a time sensitive notification that is not worth delivering late, you can set an expiration date. This avoids needlessly alerting users of information that may no longer be relevant.

There are two methods provided by the ParsePush class to allow setting an expiration date for your notification. The first is setExpirationTime which simply takes an time (in UNIX epoch time) specifying when Parse should stop trying to send the notification.

// Send push notification with expiration date
ParsePush push = new ParsePush();
push.setExpirationTime(1430168201);
push.setQuery(everyoneQuery);
push.setMessage("Season tickets on sale until April 27th");
push.sendPushInBackground();

There is however a caveat with this method. Since device clocks are not guaranteed to be accurate, you may end up with inaccurate results. For this reason, the ParsePush class also provides the setExpirationTimeInterval method which accepts a timeInterval (in seconds). The notification will expire after the specified interval has elapsed.

// Create time interval
long weekInterval = 60*60*24*7; // 1 week

// Send push notification with expiration interval
ParsePush push = new ParsePush();
push.setExpirationTimeInterval(weekInterval);
push.setQuery(everyoneQuery);
push.setMessage("Season tickets on sale until May 3rd");
push.sendPushInBackground();

Targeting by Platform

If you build a cross platform app, it is possible you may only want to target devices of a particular operating system. Advanced Targeting allow you to filter which of these devices are targeted.

The following example would send a different notification to Android, iOS, and Windows users.

ParseQuery query = ParseInstallation.getQuery();
query.whereEqualTo("channels", "suitcaseOwners");

// Notification for Android users
query.whereEqualTo("deviceType", "android");
ParsePush androidPush = new ParsePush();
androidPush.setMessage("Your suitcase has been filled with tiny robots!");
androidPush.setQuery(query);
androidPush.sendPushInBackground();

// Notification for iOS users
query.whereEqualTo("deviceType", "ios");
ParsePush iOSPush = new ParsePush();
iOSPush.setMessage("Your suitcase has been filled with tiny apples!");
iOSPush.setQuery(query);
iOSPush.sendPushInBackground();

// Notification for Windows 8 users
query.whereEqualTo("deviceType", "winrt");
ParsePush winPush = new ParsePush();
winPush.setMessage("Your suitcase has been filled with tiny glass!");
winPush.setQuery(query);
winPush.sendPushInBackground();

// Notification for Windows Phone 8 users
query.whereEqualTo("deviceType", "winphone");
ParsePush wpPush = new ParsePush();
wpPush.setMessage("Your suitcase is very hip; very metro.");
wpPush.setQuery(query);
wpPush.sendPushInBackground();

Scheduling Pushes

Sending scheduled push notifications is not currently supported by the Android SDK. Take a look at the REST API, JavaScript SDK or the Parse.com push console.

Receiving Pushes

When a push notification is received, the “title” is displayed in the status bar and the “alert” is displayed alongside the “title” when the user expands the notification drawer.

Make sure you've gone through the Android Push QuickStart to set up your app to receive pushes. The quickstart shows you how to set up push for all Android devices, including ones that do not support GCM. If you are only pushing to GCM-enabled devices, you can remove these elements from your AndroidManifest.xml:

  • The receiver element for com.parse.ParseBroadcastReceiver (including the intent filter for BOOT_COMPLETED and USER_PRESENT)
  • The permission element for android.permission.RECEIVE_BOOT_COMPLETED

You will still need all the other elements (including the one for com.parse.PushService) as described in the quickstart. If you choose to subclass com.parse.ParsePushBroadcastReceiver, be sure to replace that name with your class' name in the registration.

Note that some Android emulators (the ones missing Google API support) don't support GCM, so if you test your app in an emulator with this type of configuration make sure to select an emulator image that has Google APIs installed.

Customizing Notifications

Customizing Notification Icons

The Android style guide recommends apps use a push icon that is monochromatic and flat. The default push icon is your application's launcher icon, which is unlikely to conform to the style guide. To provide a custom push icon, add the following metadata tag to your app's AndroidManifest.xml:

<meta-data android:name="com.parse.push.notification_icon" android:resource="@drawable/push_icon"/>

where push_icon is the name of a drawable resource in your package. If your application needs more than one small icon, you can override getSmallIconId in your ParsePushBroadcastReceiver subclass.

If your push has a unique context associated with an image, such as the avatar of the user who sent a message, you can use a large push icon to call attention to the notification. When a notification has a large push icon, your app's static (small) push icon is moved to the lower right corner of the notification and the large icon takes its place. See the Anrdoid UI documentation for examples. To provide a large icon, you can override getLargeIcon in your ParsePushBroadcastReceiver subclass.

Responding with a Custom Activity

If your push has no "uri" parameter, onPushOpen will invoke your application's launcher activity To customize this behavior, you can override getActivity in your ParsePushBroadcastReceiver subclass.

Responding with a URI

If you provide a "uri" field in your push, the ParsePushBroadcastReceiver will open that URI when the notification is opened. If there are multiple apps capable of opening the URI, a dialog will displayed for the user. The ParsePushBroadcastReceiver will manage your back stack and ensure that clicking back from the Activity handling URI will navigate the user back to the activity returned by getActivity.

Managing the Push Lifecycle

The push lifecycle has three phases:
  1. A notification is received and the com.parse.push.intent.OPEN Intent is fired, causing the ParsePushBroadcastReceiver to call onPushReceive. If either "alert" or "title" are specified in the push, then a Notification is constructed using getNotification. This Notification uses a small icon generated using getSmallIconId, which defaults to the icon specified by the com.parse.push.notification_icon metadata in your AndroidManifest.xml. The Notification's large icon is generated from getLargeIcon which defaults to null. The notification's contentIntent and deleteIntent are com.parse.push.intent.OPEN and com.parse.push.intent.DELETE respectively.
  2. If the user taps on a Notification, the com.parse.push.intent.OPEN Intent is fired. The ParsePushBroadcastReceiver calls onPushOpen. The default implementation automatically sends an analytics event back to Parse tracking that this notification was opened. If the push contains a "uri" parameter, an activity is launched to navigate to that URI, otherwise the activity returned by getActivity is launched.
  3. If the user dismisses a Notification, the com.parse.push.intent.DELETE Intent is fired. The ParsePushBroadcastReceiver calls onPushDismiss, which does nothing by default

All of the above methods may be subclassed to customize the way your application handles push notifications. When subclassing the methods onPushReceive, onPushOpen, onPushDismiss, or getNotification, consider delegating to super where appropriate. For example, one might override onPushReceive to trigger a background operation for "silent" pushes and then delegate to super for all other pushes. This provides the most benefit from Parse Push and makes your code forward-compatible.

Tracking Pushes and App Opens

The default implementation of onPushOpen will automatically track user engagment from pushes. If you choose not to use the ParsePushBroadcastReceiver or override the onPushOpen implementation, you may need to track your app open event manually. To do this, add the following to the onCreate method of the Activity or the onReceive method of the BroadcastReceiver which handles the com.parse.push.intent.OPEN Intent:

ParseAnalytics.trackAppOpened(getIntent());

To track push opens, you should always pass the Intent to trackAppOpened. Passing null to trackAppOpened will track only a standard app-opened event, not the push-opened event. If you don't track the push-opened event, you will not be able to use advanced analytics features such as push-open graphs and A/B testing.

Please be sure to set up your application to save the Installation object. Push open tracking only works when your application's devices are associated with saved Installation objects.

You can view the open rate for a specific push notification on your Parse.com push console. You can also view your application's overall app open and push open graphs on the Parse analytics console. Our push open analytics graphs are rendered in real time, so you can easily verify that your application is sending the correct analytics events before your next release.

Push Experiments

You can A/B test your push notifications to figure out the best way to keep your users engaged. With A/B testing, you can simultaneously send two versions of your push notification to different devices, and use each version's push open rates to figure out which one is better. You can test by either message or send time.

A/B Testing

Our web push console guides you through every step of setting up an A/B test:

  1. For each push campaign sent through the Parse web push console, you can allocate a subset of your devices to be in the experiment's test audience, which Parse will automatically split into two equally-sized experiment groups. For each experiment group, you can specify a different push message. The remaining devices will be saved so that you can send the winning message to them later. Parse will randomly assign devices to each group to minimize the chance for a test to affect another test's results (although we still don't recommend running multiple A/B tests over the same devices on the same day).
  2. Experiment_enable Experiment_messages


  3. After you send the push, you can come back to the push console to see in real time which version resulted in more push opens, along with other metrics such as statistical confidence interval. It's normal for the number of recipients in each group to be slightly different because some devices that we had originally allocated to that experiment group may have uninstalled the app. It's also possible for the random group assignment to be slightly uneven when the test audience size is small. Since we calculate open rate separately for each group based on recipient count, this should not significantly affect your experiment results.
  4. Experiment_results


  5. If you are happy with the way one message performed, you can send that to the rest of your app's devices (i.e. the “Launch Group”). This step only applies to A/B tests where you vary the message.
  6. Experiment_launch


Push experiments are supported on all recent Parse SDKs (iOS v1.2.13+, Android v1.4.0+, .NET v1.2.7+). Before running experiments, you must instrument your app with push open tracking.

Experiment Statistics

Parse provides guidance on how to run experiments to achieve statistically significant results.

Test Audience Size

When you setup a push message experiment, we'll recommend the minimum size of your test audience. These recommendations are generated through simulations based on your app's historical push open rates. For big push campaigns (e.g. 100k+ devices), this recommendation is usually small subset of your devices. For smaller campaigns (e.g. < 5k devices), this recommendation is usually all devices. Using all devices for your test audience will not leave any remaining devices for the launch group, but you can still gain valuable insight into what type of messaging works better so you can implement similar messaging in your next push campaign.

Confidence Interval

After you send your pushes to experiment groups, we'll also provide a statistical confidence interval when your experiment has collected enough data to have statistically significant results. This confidence interval is in absolute percentage points of push open rate (e.g. if the open rates for groups A and B are 3% and 5%, then the difference is reported as 2 percentage points). This confidence interval is a measure of how much difference you would expect to see between the two groups if you repeat the same experiment many times.

Just after a push send, when only a small number of users have opened their push notifications, the open rate difference you see between groups A and B could be due to random chance, so it might not be reproducible if you run the same experiment again. After your experiment collects more data over time, we become increasingly confident that the observed difference is a true difference. As this happens, the confidence interval will become narrower, allowing us to more accurately estimate the true difference between groups A and B. Therefore, we recommend that you wait until there is enough data to generate a statistical confidence interval before deciding which group's push is better.

Troubleshooting

Setting up Push Notifications is often a source of frustration for developers. The process is complicated and invites problems to happen along the way. If you run into issues, try some of these troubleshooting tips.

  • Upgrade to the latest SDK. This documentation covers the push API introduced in the 1.7.0 version of the Android Parse SDK. Please upgrade if you are getting compiler errors following these instructions.
  • Make sure you have the correct permissions listed in your AndroidManifest.xml file, as outlined in steps 4 and 6 of the Android Push Quickstart. If you are using a a custom receiver, be sure you have registered it in the Manifest file with the correct android:name property and the proper intent filters.
  • Make sure you've used the correct App ID and client key, and that Parse.initialize() is being called. Parse.initialize() lets the service know which application it is listening for; we suggest putting this code in the Application.onCreate rather than the onCreate method for a particular Activity, so that any activation technique will know how to use Parse.
  • Check that the device is set to accept push notifications from your app.
  • Check the number of recipients in your Parse Push Console. Does it match the expected number of recipients? Your push might be targeted incorrectly.
  • If testing in the emulator, try cleaning and rebuilding your project and restarting your AVD.
  • Turn on verbose logging with Parse.setLogLevel(Parse.LOG_LEVEL_VERBOSE). The error messages will be a helpful guide to what may be going on.
  • If you see the message "Finished (with error)" in your Dashboard, check your verbose logs. If you are performing pushes from a device, check that client-side push is enabled in your dashboard.
  • In your logs, you may see an error message, "Could not construct writer" or other issues related to a broken pipe. When this occurs, the framework will continue to try reconnecting. It should not crash your app.
  • If your app has been released for a while, it's possible for the recipient estimate on the push composer page to be higher than the pushes sent value on the push results page. The push composer estimate is generated via running your push segment query over your app's installation table. We do not automatically delete installation objects when the users uninstall your app. When we try to send a push, we detect uninstalled installations and do not include them in the pushes sent value on the results page.

Push Notification Guide

If you haven't installed the SDK yet, please head over to the Push QuickStart to get our SDK up and running.

Introduction

Push Notifications are a great way to keep your users engaged and informed about your app. You can reach your entire user base quickly and effectively. This guide will help you through the setup process and the general usage of Parse to send push notifications.

The Windows SDK can send push notifications from all runtimes, but only WinRT and Windows Phone apps can receive pushes from Microsoft push servers.

Setting Up Push

Tutorial_link

Follow the Windows 8 push tutorial.

If you want to start using push on Windows 8, start by completing the Windows 8 Push tutorial to learn how to configure your app. Come back to this guide afterwards to learn more about the push features offered by Parse.

Windows Phone 8 supports authenticated and unauthenticated push notifications. Authenticated push notifications are not supported at this time. In the meantime, enjoy toast push notifications without any required setup. Some things you need to keep in mind:

  • The communication between Parse and Microsoft's cloud is unencrypted. The communication between your device and Parse will always be secure.
  • You are limited to 500 pushes per day per subscription.

Installations

Every Parse application installed on a device registered for push notifications has an associated Installation object. The Installation object is where you store all the data needed to target push notifications. For example, in a baseball app, you could store the teams a user is interested in to send updates about their performance. Saving the Installation object is also required for tracking push-related app open events.

On Windows, Installation objects are available through the ParseInstallation class, a subclass of ParseObject. It uses the same API for storing and retrieving data. To access the current Installation object from your .NET app, use the ParseInstallation.CurrentInstallation property. The first time you save a ParseInstallation, Parse will add it to your Installation class, and it will be available for targeting push notifications as long as its deviceUris field is set.

protected override async void OnLaunched(LaunchActivatedEventArgs args) {
  await ParseInstallation.CurrentInstallation.SaveAsync();
}

While it is possible to modify a ParseInstallation just like you would a ParseObject, there are several special fields that help manage and target devices.

  • channels: An IEnumerable<string> of the channels to which a device is currently subscribed. In .NET, this field is accessible through the Channels property.
  • timeZone: The current time zone where the target device is located. This field is readonly and can be accessed via the TimeZone property. This value is synchronized every time an Installation object is saved from the device.
  • deviceType: The type of device, "ios", "android", "winrt", "winphone", or "dotnet". This field is readonly and can be accessed via the DeviceType property.
  • pushType: This field is reserved for directing Parse to the push delivery network to be used. If the device is registered to receive pushes via GCM, this field will be marked "gcm". If this device is not using GCM, and is using Parse's push notification service, it will be blank (readonly).
  • installationId: Unique Id for the device used by Parse. This field is readonly and can be accessed via the InstallationId property.
  • deviceToken: The Apple generated token used for iOS devices (readonly).
  • channelUris: The Microsoft-generated push URIs for Windows devices. This field is readonly and can be accessed via the DeviceUris property.
  • appName: The display name of the client application to which this installation belongs. This field is readonly and can be accessed via the AppName property.
  • appVersion: The version string of the client application to which this installation belongs. This field is readonly and can be accessed via the AppVersion property.
  • parseVersion: The version of the Parse SDK which this installation uses. This field is readonly and can be accessed via the ParseVersion property.
  • appIdentifier: A unique identifier for this installation's client application. This field is readonly and can be accessed via the AppIdentifier property. On Windows 8, this is the Windows.ApplicationModel.Package id; on Windows Phone 8 this is the ProductId; in other .NET applications, this is the ApplicationIdentity of the current AppDomain

Sending Pushes

There are two ways to send push notifications using Parse: channels and advanced targeting. Channels offer a simple and easy to use model for sending pushes, while advanced targeting offers a more powerful and flexible model. Both are fully compatible with each other and will be covered in this section.

Sending notifications is often done from the Parse.com push console, the REST API or from Cloud Code. However, push notifications can also be triggered by the existing client SDKs. If you decide to send notifications from the client SDKs, you will need to set Client Push Enabled in the Push Notifications settings of your Parse app.

However, be sure you understand that enabling Client Push can lead to a security vulnerability in your app, as outlined on our blog. We recommend that you enable Client Push for testing purposes only, and move your push notification logic into Cloud Code when your app is ready to go into production.

Client_push_settings

You can view your past push notifications on the Parse.com push console for up to 30 days after creating your push. For pushes scheduled in the future, you can delete the push on the push console as long as no sends have happened yet. After you send the push, the push console shows push analytics graphs.

Using Channels

The simplest way to start sending notifications is using channels. This allows you to use a publisher-subscriber model for sending pushes. Devices start by subscribing to one or more channels, and notifications can later be sent to these subscribers. The channels subscribed to by a given Installation are stored in the channels field of the Installation object.

Subscribing to Channels

A channel is identified by a string that starts with a letter and consists of alphanumeric characters, underscores, and dashes. It doesn't need to be explicitly created before it can be used and each Installation can subscribe to any number of channels at a time.

An installation's channels can be set using the Channels property of ParseInstallation. For example, in a baseball socre app, we could do:

// When users indicate they are Giants fans, we subscribe them to that channel.
var installation = ParseInstallation.CurrentInstallation;
installation.Channels = new List<string> { "Giants" };
await installation.SaveAsync();

Alternatively, you can insert a channel into Channels without affecting the existing channels using the AddUniqueToList method of ParseObject using the following:

var installation = ParseInstallation.CurrentInstallation;
installation.AddUniqueToList("channels", "Giants");
await installation.SaveAsync();

Finally, ParsePush provides a shorthand for inserting a channel into Channels and saving:

await ParsePush.SubscribeAsync("Giants");

Once subscribed to the "Giants" channel, your Installation object should have an updated channels field.

Installation_channel

Unsubscribing from a channel is just as easy:

var installation = ParseInstallation.CurrentInstallation;
installation.RemoveAllFromList("channels" new List<string> { "Giants" });
await installation.SaveAsync();

Or, using ParsePush:

ParsePush.UnsubscribeAsync("Giants");

The set of subscribed channels is cached in the CurrentInstallation object:

var installation = ParseInstallation.CurrentInstallation
IEnumerable<string> subscribedChannels = installation.Channels;

If you plan on changing your channels from Cloud Code or the data browser, note that you'll need to call FetchAsync prior to this line in order to get the most recent channels.

Sending Pushes to Channels

In the .NET SDK, the following code can be used to alert all subscribers of the "Giants" channel that their favorite team just scored. This will display a toast notification to Windows users. iOS users will receive a notification in the notification center and Android users will receive a notification in the system tray.

// Send a notification to all devices subscribed to the "Giants" channel.
var push = new ParsePush();
push.Channels = new List<string> {"Giants"};
push.Alert = "The Giants just scored!";
await push.SendAsync();

If you want to target multiple channels with a single push notification, you can use any IEnumerable<string> of channels.

Using Advanced Targeting

While channels are great for many applications, sometimes you need more precision when targeting the recipients of your pushes. Parse allows you to write a query for any subset of your Installation objects using the querying API and to send them a push.

Since ParseInstallation is a subclass of ParseObject, you can save any data you want and even create relationships between Installation objects and your other objects. This allows you to send pushes to a very customized and dynamic segment of your user base.

Saving Installation Data

Storing data on an Installation object is just as easy as storing any other data on Parse. In our Baseball app, we could allow users to get pushes about game results, scores and injury reports.

// Store the category of push notifications the user would like to receive.
var installation = ParseInstallation.CurrentInstallation;
installation["scores"] = true;
installation["gameResults"] = true;
installation["injuryReports"] = true;
await installation.SaveAsync();

You can even create relationships between your Installation objects and other classes saved on Parse. To associate an Installation with a particular user, for example, you can simply store the current user on the ParseInstallation.

// Associate the device with a user
var installation = ParseInstallation.CurrentInstallation;
installation["user"] = ParseUser.CurrentUser;
await installation.SaveAsync();

Sending Pushes to Queries

Once you have your data stored on your Installation objects, you can use a ParseQuery to target a subset of these devices. Installation queries work just like any other Parse query, but we use the special static property ParseInstallation.Query to create it. We set this query on our ParsePush object, before sending the notification.

var push = new ParsePush();
push.Query = from installation in ParseInstallation.Query
             where installation.Get<bool>("injuryReports") == true
             select installation;
push.Alert = "Willie Hayes injured by own pop fly.";
await push.SendAsync();

We can even use channels with our query. To send a push to all subscribers of the "Giants" channel but filtered by those who want score update, we can do the following:

var push = new Parse.Push();
push.Query = from installation in ParseInstallation.Query
             where installation.Get<bool>("scores") == true
             select installation;
push.Channels = new List<string> { "Giants" };
push.Alert = "Giants scored against the A's! It's now 2-2.";
await push.SendAsync();

Alternatively, we can use a query that constrains "channels" directly:

var push = new Parse.Push();
push.Query = from installation in ParseInstallation.Query
             where installation.Get<bool>("scores") == true
             where installation.Channels.Contains("Giants")
             select installation;
push.Alert = "Giants scored against the A's! It's now 2-2.";
await push.SendAsync();

If we store relationships to other objects in our Installation class, we can also use those in our query. For example, we could send a push notification to all users near a given location like this.

// Find users in the Seattle metro area
var userQuery = ParseUser.Query.WhereWithinDistance(
    "location",
    marinersStadium,
    ParseGeoDistance.FromMiles(1));
var push= new ParsePush();
push.Query = from installation in ParseInstallation.Query
             join user in userQuery on installation["user"] equals user
             select installation;
push.Alert = "Mariners lost? Free conciliatory hotdogs at the Parse concession stand!";
await push.SendAsync();

Sending Options

Push notifications can do more than just send a message. On Windows and Windows Phone 8, pushes can also include a title, as well as any custom data you wish to send. An expiration date can also be set for the notification in case it is time sensitive.

Customizing your Notifications

If you want to send more than just a message, you will need to use an IDictionary<string, object> to package all of the data. There are some reserved fields that have a special meaning.

  • alert: the notification's message.
  • badge: (iOS only) the value indicated in the top right corner of the app icon. This can be set to a value or to Increment in order to increment the current value by 1.
  • sound: (iOS only) the name of a sound file in the application bundle.
  • content-available: (iOS only) If you are a writing a Newsstand app, or an app using the Remote Notification Background Mode introduced in iOS7 (a.k.a. "Background Push"), set this value to 1 to trigger a background download.
  • category: (iOS only) the identifier of the UIUserNotificationCategory for this push notification.
  • uri: (Android only) an optional field that contains a URI. When the notification is opened, an Activity associated with opening the URI is launched.
  • title: (Android, Windows 8, and Windows Phone 8 only) the value displayed in the Android system tray or Windows toast notification.

For example, to send a notification that contains a title, you can do the following:

var push = new ParsePush();
push.Channels = new List<string> {"Mets"};
push.Data = new Dictionary<string, object> {
  {"title", "Score Alert"}
  {"alert", "The Mets scored! The game is now tied 1-1!"},
};
await push.SendAsync();

Setting an Expiration Date

When a user's device is turned off or not connected to the internet, push notifications cannot be delivered. If you have a time sensitive notification that is not worth delivering late, you can set an expiration date. This avoids needlessly alerting users of information that may no longer be relevant.

There are two properties provided by the ParsePush class to allow setting an expiration date for your notification. The first is Expiration which simply takes a DateTime? specifying when Parse should stop trying to send the notification.

var push = new ParsePush();
push.Expiration = new DateTime(2015, 4, 27);
push.Alert = "Season tickets on sale until April 27th";
await push.SendAsync();

There is however a caveat with this method. Since device clocks are not guaranteed to be accurate, you may end up with inaccurate results. For this reason, the ParsePush class also provides the ExpirationInterval property which accepts a TimeSpan. The notification will expire after the specified interval has elapsed.

var push = new ParsePush();
push.ExpirationInterval = TimeSpan.FromDays(7);
push.Alert = "Season tickets on sale until May 3rd";
await push.SendAsync();

Targeting by Platform

If you build a cross platform app, it is possible you may only want to target one operating system. There are two methods provided to filter which of these devices are targeted. Note that all platforms are targeted by default.

The following example would send a different notification to Android, iOS, and Windows users.

// Notification for Android users
var androidPush = new ParsePush();
androidPush.Alert = "Your suitcase has been filled with tiny robots!";
androidPush.Query = from installation in ParseInstallation.Query
                    where installation.Channels.Contains("suitcaseOwners")
                    where installation.DeviceType == "android"
                    select installation;
await androidPush.SendAsync();

// Notification for iOS users
var iOSPush = new ParsePush();
iosPush.Alert = "Your suitcase has been filled with tiny apples!";
iosPush.Query = from installation in ParseInstallation.Query
                where installation.Channels.Contains("suitcaseOwners")
                where installation.DeviceType == "ios"
                select installation;
await iosPush.SendAsync();

// Notification for Windows 8 users
var winPush = new ParsePush();
winPush.Alert = "Your suitcase has been filled with tiny glass!";
winPush.Query = from installation in ParseInstallation.Query
                where installation.Channels.Contains("suitcaseOwners")
                where installation.DeviceType == "winrt"
                select installation;
await winPush.SendAsync();

// Notification for Windows Phone 8 users
var wpPush = new ParsePush();
wpPush.Alert = "Your suitcase is very hip; very metro."
wpPush.Query = from installation in ParseInstallation.Query
               where installation.Channels.Contains("suitcaseOwners")
               where installation.DeviceType == "winphone"
               select installation;
await winPhonePush.SendAsync();

Scheduling Pushes

Sending scheduled push notifications is not currently supported by the .NET SDK. Take a look at the REST API, JavaScript SDK or the Parse.com push console.

Receiving Pushes

Responding to the Payload

If your app is running while a push notification is received, the ParsePush.PushNotificationReceived event is fired. You can register for this event. This event provides the standard PushNotificationEventArgs. When Parse sends a toast notification, it embeds the JSON payload in the notification. You can extract this JSON payload with ParsePush.PushJson.

ParsePush.ToastNotificationReceived += (sender, args) => {
  var json = ParsePush.PushJson(args);
  object objectId;
  if (json.TryGetValue("objectId", out objectId)) {
    DisplayRichMessageWithObjectId(objectId as string);
  }
};

In Windows 8, you may also receive the JSON payload from the LaunchActivatedEventArgs passed to your application's OnLaunched event.

protected override void OnLaunched(LaunchActivatedEventArgs args) {
  var json = ParsePush.PushJson(args);
  object objectId;
  if (json.TryGetValue("objectId", out objectId)) {
    DisplayRichMessageWithObjectId(objectId as string);
  }
};

In Windows Phone 8, this code would instead be in your page's OnNavigatedTo event:

public override void OnNavigatedTo(NavigationEventArgs args) {
  var json = ParsePush.PushJson(args);
  object objectId;
  if (json.TryGetValue("objectId", out objectId)) {
    DisplayRichMessageWithObjectId(objectId as string);
  }
}

Tracking Pushes and App Opens

To track your users' engagement over time and the effect of push notifications, we provide some hooks in the ParseAnalytics class. In the example above, add the following to your Launching event handler to collect information about when your application was opened, and what triggered it.

ParseAnalytics.TrackAppOpenedAsync(launchArgs);

To track push opens, you should always pass your event handler's input args to TrackAppOpenedAsync. A null or empty parameter to TrackAppOpenedAsync will track only a standard app-opened event, not the push-opened event. If you don't track the push-opened event, you will not be able to use advanced analytics features such as push-open graphs and A/B testing.

Please be sure to set up your application to save the Installation object. Push open tracking only works when your application's devices are associated with saved Installation objects.

You can view the open rate for a specific push notification on your Parse.com push console. You can also view your application's overall app open and push open graphs on the Parse analytics console. Our push open analytics graphs are rendered in real time, so you can easily verify that your application is sending the correct analytics events before your next release.

Tracking on WinRT Applications

On Windows 8, a toast notification can pass a small payload to the launch handler. We take advantage of this to pass a small Parse payload to the app, in order to correlate an app launch with a particular push.

// Override Application.OnLaunched
virtual void OnLaunched(LaunchActivatedEventArgs args) {
    // 'args' contains arguments that are passed to the app
    // during its launch activation from a Toast.
    // More on Toasts: http://msdn.microsoft.com/en-us/library/windows/apps/hh779727.aspx
    ParseAnalytics.TrackAppOpenedAsync(args);
}

Tracking on Windows Phone Applications

You can also track application launches from toast notifications in Windows Phone 8. To track application launches from toasts and tiles, add the following to your App constructor:

this.Startup += (sender, args) => {
  ParseAnalytics.TrackAppOpens(RootFrame);
};

This method will set up event handlers necessary to track all app launches; you should not use TrackAppOpenedAsync if you register event handlers with TrackAppOpens.

Push Experiments

You can A/B test your push notifications to figure out the best way to keep your users engaged. With A/B testing, you can simultaneously send two versions of your push notification to different devices, and use each version's push open rates to figure out which one is better. You can test by either message or send time.

A/B Testing

Our web push console guides you through every step of setting up an A/B test:

  1. For each push campaign sent through the Parse web push console, you can allocate a subset of your devices to be in the experiment's test audience, which Parse will automatically split into two equally-sized experiment groups. For each experiment group, you can specify a different push message. The remaining devices will be saved so that you can send the winning message to them later. Parse will randomly assign devices to each group to minimize the chance for a test to affect another test's results (although we still don't recommend running multiple A/B tests over the same devices on the same day).
  2. Experiment_enable Experiment_messages


  3. After you send the push, you can come back to the push console to see in real time which version resulted in more push opens, along with other metrics such as statistical confidence interval. It's normal for the number of recipients in each group to be slightly different because some devices that we had originally allocated to that experiment group may have uninstalled the app. It's also possible for the random group assignment to be slightly uneven when the test audience size is small. Since we calculate open rate separately for each group based on recipient count, this should not significantly affect your experiment results.
  4. Experiment_results


  5. If you are happy with the way one message performed, you can send that to the rest of your app's devices (i.e. the “Launch Group”). This step only applies to A/B tests where you vary the message.
  6. Experiment_launch


Push experiments are supported on all recent Parse SDKs (iOS v1.2.13+, Android v1.4.0+, .NET v1.2.7+). Before running experiments, you must instrument your app with push open tracking.

Experiment Statistics

Parse provides guidance on how to run experiments to achieve statistically significant results.

Test Audience Size

When you setup a push message experiment, we'll recommend the minimum size of your test audience. These recommendations are generated through simulations based on your app's historical push open rates. For big push campaigns (e.g. 100k+ devices), this recommendation is usually small subset of your devices. For smaller campaigns (e.g. < 5k devices), this recommendation is usually all devices. Using all devices for your test audience will not leave any remaining devices for the launch group, but you can still gain valuable insight into what type of messaging works better so you can implement similar messaging in your next push campaign.

Confidence Interval

After you send your pushes to experiment groups, we'll also provide a statistical confidence interval when your experiment has collected enough data to have statistically significant results. This confidence interval is in absolute percentage points of push open rate (e.g. if the open rates for groups A and B are 3% and 5%, then the difference is reported as 2 percentage points). This confidence interval is a measure of how much difference you would expect to see between the two groups if you repeat the same experiment many times.

Just after a push send, when only a small number of users have opened their push notifications, the open rate difference you see between groups A and B could be due to random chance, so it might not be reproducible if you run the same experiment again. After your experiment collects more data over time, we become increasingly confident that the observed difference is a true difference. As this happens, the confidence interval will become narrower, allowing us to more accurately estimate the true difference between groups A and B. Therefore, we recommend that you wait until there is enough data to generate a statistical confidence interval before deciding which group's push is better.

Troubleshooting

Setting up Push Notifications is often a source of frustration for developers. The process is complicated and invites problems to happen along the way. If you run into issues, try some of these troubleshooting tips.

  • Make sure you are using the correct Package SID and client secret, as shown in Step 3 of the Windows 8 Push Quickstart.
  • Clean and build your project.
  • Check the number of recipients in your Parse Push Console. Does it match the expected number of recipients? Your push might be targeted incorrectly.
  • Open your project's package.appxmanifest file and make sure "Toast Capable" is set to "yes."
  • If your app has been released for a while, it's possible for the recipient estimate on the push composer page to be higher than the pushes sent value on the push results page. The push composer estimate is generated via running your push segment query over your app's installation table. We do not automatically delete installation objects when the users uninstall your app. When we try to send a push, we detect uninstalled installations and do not include them in the pushes sent value on the results page.

Push Notification Guide

If you haven't installed the SDK yet, please head over to the Push QuickStart to get our SDK up and running.

Introduction

Push Notifications are a great way to keep your users engaged and informed about your app. You can reach your entire user base quickly and effectively. This guide will help you through the setup process and the general usage of Parse to send push notifications.

The JavaScript SDK does not currently support receiving pushes. It can only be used to send notifications to iOS and Android applications. A common use case is to send pushes from Cloud Code.

Setting Up Push

There is no setup required to use the JavaScript SDK for sending push notifications. If you haven't configured your iOS or Android clients to use Push, take a look at their respective setup instruction using the platform toggle at the top.

Installations

Every Parse application installed on a device registered for push notifications has an associated Installation object. The Installation object is where you store all the data needed to target push notifications. For example, in a baseball app, you could store the teams a user is interested in to send updates about their performance.

Note that Installation data can only be modified by the client SDKs, the data browser, or the REST API.

This class has several special fields that help you manage and target devices.

  • badge: The current value of the icon badge for iOS apps. Changes to this value on the server will be used for future badge-increment push notifications.
  • channels: An array of the channels to which a device is currently subscribed.
  • timeZone: The current time zone where the target device is located. This value is synchronized every time an Installation object is saved from the device.
  • deviceType: The type of device, "ios", "android", "winrt", "winphone", or "dotnet"(readonly).
  • pushType: This field is reserved for directing Parse to the push delivery network to be used. If the device is registered to receive pushes via GCM, this field will be marked "gcm". If this device is not using GCM, and is using Parse's push notification service, it will be blank (readonly).
  • installationId: Universally Unique Identifier (UUID) for the device used by Parse. It must be unique across all of an app's installations. (readonly).
  • deviceToken: The Apple or Google generated token used to deliver messages to the APNs or GCM push networks respectively.
  • channelUris: The Microsoft-generated push URIs for Windows devices.
  • appName: The display name of the client application to which this installation belongs.
  • appVersion: The version string of the client application to which this installation belongs.
  • parseVersion: The version of the Parse SDK which this installation uses.
  • appIdentifier: A unique identifier for this installation's client application. In iOS, this is the Bundle Identifier.

Sending Pushes

There are two ways to send push notifications using Parse: channels and advanced targeting. Channels offer a simple and easy to use model for sending pushes, while advanced targeting offers a more powerful and flexible model. Both are fully compatible with each other and will be covered in this section.

Sending notifications is often done from the Parse.com push console, the REST API or from Cloud Code. Since the JavaScript SDK is used in Cloud Code, this is the place to start if you want to send pushes from your Cloud Functions. However, if you decide to send notifications from the JavaScript SDK outside of Cloud Codeor any of the other client SDKs, you will need to set Client Push Enabled in the Push Notifications settings of your Parse app.

However, be sure you understand that enabling Client Push can lead to a security vulnerability in your app, as outlined on our blog. We recommend that you enable Client Push for testing purposes only, and move your push notification logic into Cloud Code when your app is ready to go into production.

Client_push_settings

You can view your past push notifications on the Parse.com push console for up to 30 days after creating your push. For pushes scheduled in the future, you can delete the push on the push console as long as no sends have happened yet. After you send the push, the push console shows push analytics graphs.

Using Channels

The simplest way to start sending notifications is using channels. This allows you to use a publisher-subscriber model for sending pushes. Devices start by subscribing to one or more channels, and notifications can later be sent to these subscribers. The channels subscribed to by a given Installation are stored in the channels field of the Installation object.

Subscribing to Channels

The JavaScript SDK does not currently support subscribing iOS and Android devices for pushes. Take a look at the iOS, Android or REST Push guide using the platform toggle at the top.

Sending Pushes to Channels

With the JavaScript SDK, the following code can be used to alert all subscribers of the "Giants" and "Mets" channels about the results of the game. This will display a notification center alert to iOS users and a system tray notification to Android users.

Parse.Push.send({
  channels: [ "Giants", "Mets" ],
  data: {
    alert: "The Giants won against the Mets 2-3."
  }
}, {
  success: function() {
    // Push was successful
  },
  error: function(error) {
    // Handle error
  }
});

Using Advanced Targeting

While channels are great for many applications, sometimes you need more precision when targeting the recipients of your pushes. Parse allows you to write a query for any subset of your Installation objects using the querying API and to send them a push.

Since Installation objects are just like any other object stored in Parse, you can save any data you want and even create relationships between Installation objects and your other objects. This allows you to send pushes to a very customized and dynamic segment of your user base.

Saving Installation Data

The JavaScript SDK does not currently support modifying Installation objects. Take a look at the iOS, Android or REST Push guide using the platform toggle at the top.

Sending Pushes to Queries

Once you have your data stored on your Installation objects, you can use a query to target a subset of these devices. Parse.Installation queries work just like any other Parse query.

var query = new Parse.Query(Parse.Installation);
query.equalTo('injuryReports', true);

Parse.Push.send({
  where: query, // Set our Installation query
  data: {
    alert: "Willie Hayes injured by own pop fly." 
  }
}, {
  success: function() {
    // Push was successful
  },
  error: function(error) {
    // Handle error
  }
});

We can even use channels with our query. To send a push to all subscribers of the "Giants" channel but filtered by those who want score update, we can do the following:

var query = new Parse.Query(Parse.Installation);
query.equalTo('channels', 'Giants'); // Set our channel
query.equalTo('scores', true);

Parse.Push.send({
  where: query,
  data: {
    alert: "Giants scored against the A's! It's now 2-2." 
  }
}, {
  success: function() {
    // Push was successful
  },
  error: function(error) {
    // Handle error
  }
});

If we store relationships to other objects in our Installation class, we can also use those in our query. For example, we could send a push notification to all users near a given location like this.

// Find users near a given location
var userQuery = new Parse.Query(Parse.User);
userQuery.withinMiles("location", stadiumLocation, 1.0);

// Find devices associated with these users
var pushQuery = new Parse.Query(Parse.Installation);
pushQuery.matchesQuery('user', userQuery);

// Send push notification to query
Parse.Push.send({
  where: pushQuery,
  data: {
    alert: "Free hotdogs at the Parse concession stand!" 
  }
}, {
  success: function() {
    // Push was successful
  },
  error: function(error) {
    // Handle error
  }
});

Sending Options

Push notifications can do more than just send a message. In iOS, pushes can also include the sound to be played, the badge number to display as well as any custom data you wish to send. In Android, it is even possible to specify an Intent to be fired upon receipt of a notification. An expiration date can also be set for the notification in case it is time sensitive.

Customizing your Notifications

If you want to send more than just a message, you can set other fields in the data dictionary. There are some reserved fields that have a special meaning.

  • alert: the notification's message.
  • badge: (iOS only) the value indicated in the top right corner of the app icon. This can be set to a value or to Increment in order to increment the current value by 1.
  • sound: (iOS only) the name of a sound file in the application bundle.
  • content-available: (iOS only) If you are a writing a Newsstand app, or an app using the Remote Notification Background Mode introduced in iOS7 (a.k.a. "Background Push"), set this value to 1 to trigger a background download.
  • category: (iOS only) the identifier of the UIUserNotificationCategory for this push notification.
  • uri: (Android only) an optional field that contains a URI. When the notification is opened, an Activity associated with opening the URI is launched.
  • title: (Android only) the value displayed in the Android system tray notification.

For example, to send a notification that increases the current badge number by 1 and plays a custom sound for iOS devices, and displays a particular title for Android users, you can do the following:

Parse.Push.send({
  channels: [ "Mets" ],
  data: {
    alert: "The Mets scored! The game is now tied 1-1.",
    badge: "Increment",
    sound: "cheering.caf",
    title: "Mets Score!"
  }
}, {
  success: function() {
    // Push was successful
  },
  error: function(error) {
    // Handle error
  }
});

It is also possible to specify your own data in this dictionary. As explained in the Receiving Notifications section for iOS and Android, iOS will give you access to this data only when the user opens your app via the notification and Android will provide you this data in the Intent if one is specified.

var query = new Parse.Query(Parse.Installation);
query.equalTo('channels', 'Indians');
query.equalTo('injuryReports', true);

Parse.Push.send({
  where: query,
  data: {
    action: "com.example.UPDATE_STATUS"
    alert: "Ricky Vaughn was injured in last night's game!",
    name: "Vaughn",
    newsItem: "Man bites dog"
  }
}, {
  success: function() {
    // Push was successful
  },
  error: function(error) {
    // Handle error
  }
});

Setting an Expiration Date

When a user's device is turned off or not connected to the internet, push notifications cannot be delivered. If you have a time sensitive notification that is not worth delivering late, you can set an expiration date. This avoids needlessly alerting users of information that may no longer be relevant.

There are two parameters provided by Parse to allow setting an expiration date for your notification. The first is expiration_time which takes a Date specifying when Parse should stop trying to send the notification. To expire the notification exactly 1 week from now, you can use the following:

Parse.Push.send({
  where: everyoneQuery,
  expiration_time: new Date(2015, 5, 3)
  data: {
    alert: "Season tickets on sale until May  3, 2015"
  }
}, {
  success: function() {
    // Push was successful
  },
  error: function(error) {
    // Handle error
  }
});

Alternatively, you can use the expiration_interval parameter to specify a duration of time before your notification expires. This value is relative to the push_time parameter used to schedule notifications. This means that a push notification scheduled to be sent out in 1 day and an expiration interval of 6 days can be received up to a week from now.

Parse.Push.send({
  push_time: new Date(2015, 4, 27),
  expiration_interval: 518400,
  data: {
    alert: "Season tickets on sale until May  3, 2015"
  
}, {
  success: function() {
    // Push was successful
  },
  error: function(error) {
    // Handle error
  }
});

Targeting by Platform

If you build a cross platform app, it is possible you may only want to target iOS or Android devices. There are two methods provided to filter which of these devices are targeted. Note that both platforms are targeted by default.

The following examples would send a different notification to Android and iOS users.

// Notification for Android users
var queryAndroid = new Parse.Query(Parse.Installation);
queryAndroid.equalTo('deviceType', 'android');

Parse.Push.send({
  where: queryAndroid,
  data: {
    alert: "Your suitcase has been filled with tiny robots!" 
  }
});

// Notification for iOS users
var queryIOS = new Parse.Query(Parse.Installation);
queryIOS.equalTo('deviceType', 'ios');

Parse.Push.send({
  where: queryIOS,
  data: {
    alert: "Your suitcase has been filled with tiny apples!" 
  }
});

// Notification for Windows 8 users
var queryWindows = new Parse.Query(Parse.Installation);
queryWindows.equalTo('deviceType', 'winrt');

Parse.Push.send({
  where: queryWindows,
  data: {
    alert: "Your suitcase has been filled with tiny glass!" 
  }
});

// Notification for Windows Phone 8 users
var queryWindowsPhone = new Parse.Query(Parse.Installation);
queryWindowsPhone.equalTo('deviceType', 'winphone');

Parse.Push.send({
  where: queryWindowsPhone,
  data: {
    alert: "Your suitcase is very hip; very metro." 
  }
});

Scheduling Pushes

You can schedule a push in advance by specifying a push_time. For example, if a user schedules a game reminder for a game on May 3, 2015 at noon UTC, you can schedule the push notification by sending:

var query = new Parse.Query(Parse.Installation);
query.equalTo('user_id', 'user_123');

Parse.Push.send({
  where: query,
  data: {
    alert: "You previously created a reminder for the game today" 
  },
  push_time: new Date(2015, 5, 3)
}, {
  success: function() {
    // Push was successful
  },
  error: function(error) {
    // Handle error
  }
});

If you also specify an expiration_interval, it will be calculated from the scheduled push time, not from the time the push is submitted. This means a push scheduled to be sent in a week with an expiration interval of a day will expire 8 days after the request is sent.

The scheduled time cannot be in the past, and can be up to two weeks in the future. It can be an ISO 8601 date with a date, time, and timezone, as in the example above, or it can be a numeric value representing a UNIX epoch time in seconds (UTC). To schedule an alert for May 3, 2015 at noon UTC time, you can set the push_time to either 2015-05-03T12:00:00Z or 1430654400.

Receiving Pushes

The JavaScript SDK does not currently support receiving pushes. To learn more about handling received notifications in iOS or Android, use the platform toggle at the top.

Troubleshooting

For tips on troubleshooting push notifications, check the troubleshooting sections for iOS, Android, and .NET using the platform toggle at the top.

Push Notification Guide

If you haven't installed the SDK yet, please head over to the Push QuickStart to get our SDK up and running.

Introduction

Push Notifications are a great way to keep your users engaged and informed about your app. You can reach your entire user base quickly and effectively. This guide will help you through the setup process and the general usage of Parse to send push notifications.

Setting Up Push

There is no setup required to use the REST API for sending push notifications. If you haven't configured your iOS or Android client to use Push, take a look at their respective setup instructions using the platform toggle at the top.

Installations

Every Parse application installed on a device registered for push notifications has an associated Installation object. The Installation object is where you store all the data needed to target push notifications. For example, in a baseball app, you could store the teams a user is interested in to send updates about their performance.

Using the REST API, Installation objects are available through the Installation end point. This end point behaves a lot like the Object end point and uses the same API for storing and retrieving data.

Note that Installation data is typically modified by the client SDKs. The REST API can be useful for importing or exporting large amounts of subscription data.

The Installation class has several special fields that help you manage and target devices.

  • badge: The current value of the icon badge for iOS apps. Changes to this value on the server will be used for future badge-increment push notifications.
  • channels: An array of the channels to which a device is currently subscribed.
  • timeZone: The current time zone where the target device is located. This value is synchronized every time an Installation object is saved from the device.
  • deviceType: The type of device, "ios", "android", "winrt", "winphone", or "dotnet"(readonly).
  • pushType: This field is reserved for directing Parse to the push delivery network to be used. If the device is registered to receive pushes via GCM, this field will be marked "gcm". If this device is not using GCM, and is using Parse's push notification service, it will be blank (readonly).
  • GCMSenderId: This field only has meaning for Android installations that use the GCM push type. It is reserved for directing Parse to send pushes to this installation with an alternate GCM sender ID. This field should generally not be set unless you are uploading installation data from another push provider. If you set this field, then you must set the GCM API key corresponding to this GCM sender ID in your Parse application's push settings page.
  • installationId: Universally Unique Identifier (UUID) for the device used by Parse. It must be unique across all of an app's installations. (readonly).
  • deviceToken: The Apple or Google generated token used to deliver messages to the APNs or GCM push networks respectively.
  • channelUris: The Microsoft-generated push URIs for Windows devices.
  • appName: The display name of the client application to which this installation belongs.
  • appVersion: The version string of the client application to which this installation belongs.
  • parseVersion: The version of the Parse SDK which this installation uses.
  • appIdentifier: A unique identifier for this installation's client application. In iOS, this is the Bundle Identifier.

Sending Pushes

There are two ways to send push notifications using Parse: channels and advanced targeting. Channels offer a simple and easy to use model for sending pushes, while advanced targeting offers a more powerful and flexible model. Both are fully compatible with each other and will be covered in this section.

You can view your past push notifications on the Parse.com push console for up to 30 days after creating your push. For pushes scheduled in the future, you can delete the push on the push console as long as no sends have happened yet. After you send the push, the push console shows push analytics graphs.

Using Channels

The simplest way to start sending notifications is using channels. This allows you to use a publisher-subscriber model for sending pushes. Devices start by subscribing to one or more channels, and notifications can later be sent to these subscribers. The channels subscribed to by a given Installation are stored in the channels field of the Installation object.

Subscribing to Channels

A channel is identified by a string that starts with a letter and consists of alphanumeric characters, underscores, and dashes. It doesn't need to be explicitly created before it can be used and each Installation can subscribe to any number of channels at a time.

Subscribing to a channel via the REST API can be done by updating the Installation object. We send a PUT request to the Installation URL and update the channels field. For example, in a baseball score app, we could do:

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('PUT', '/1/installations/mrmBZvsErB', json.dumps({
       "channels": [
         "Giants"
       ]
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X PUT \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "channels": [
          "Giants"
        ]
      }' \
  https://api.parse.com/1/installations/mrmBZvsErB

Once subscribed to the "Giants" channel, your Installation object should have an updated channels field.

Installation_channel

To unsubscribe from a channel you would need to update the channels array and remove the unsubscribed channel.

Sending Pushes to Channels

With the REST API, the following code can be used to alert all subscribers of the "Giants" and "Mets" channels about the results of the game. This will display a notification center alert to iOS users and a system tray notification to Android users.

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "channels": [
         "Giants",
         "Mets"
       ],
       "data": {
         "alert": "The Giants won against the Mets 2-3."
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "channels": [
          "Giants",
          "Mets"
        ],
        "data": {
          "alert": "The Giants won against the Mets 2-3."
        }
      }' \
  https://api.parse.com/1/push

Using Advanced Targeting

While channels are great for many applications, sometimes you need more precision when targeting the recipients of your pushes. Parse allows you to write a query for any subset of your Installation objects using the querying API and to send them a push.

Since Installation objects are just like any other object stored in Parse, you can save any data you want and even create relationships between Installation objects and your other objects. This allows you to send pushes to a very customized and dynamic segment of your user base.

Saving Installation Data

Storing arbitrary data on an Installation object is done in the same way we store data on any other object on Parse. In our Baseball app, we could allow users to get pushes about game results, scores and injury reports.

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('PUT', '/1/installations/mrmBZvsErB', json.dumps({
       "scores": True,
       "gameResults": True,
       "injuryReports": True
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X PUT \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "scores": true,
        "gameResults": true,
        "injuryReports": true
      }' \
  https://api.parse.com/1/installations/mrmBZvsErB

You can even create relationships between your Installation objects and other classes saved on Parse. To associate an Installation with a particular user, for example, you can use a pointer to the _User class on the Installation.

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('PUT', '/1/installations/mrmBZvsErB', json.dumps({
       "user": {
         "__type": "Pointer",
         "className": "_User",
         "objectId": "vmRZXZ1Dvo"
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X PUT \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "user": {
          "__type": "Pointer",
          "className": "_User",
          "objectId": "vmRZXZ1Dvo"
        }
      }' \
  https://api.parse.com/1/installations/mrmBZvsErB

Sending Pushes to Queries

Once you have your data stored on your Installation objects, you can use a query to target a subset of these devices. Installation queries work just like any other Parse query.

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "where": {
         "injuryReports": True
       },
       "data": {
         "alert": "Willie Hayes injured by own pop fly."
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "where": {
          "injuryReports": true
        },
        "data": {
          "alert": "Willie Hayes injured by own pop fly."
        }
      }' \
  https://api.parse.com/1/push

We can even use channels with our query. To send a push to all subscribers of the "Giants" channel but filtered by those who want score update, we can do the following:

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "where": {
         "channels": "Giants",
         "scores": True
       },
       "data": {
         "alert": "The Giants scored a run! The score is now 2-2."
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "where": {
          "channels": "Giants",
          "scores": true
        },
        "data": {
          "alert": "The Giants scored a run! The score is now 2-2."
        }
      }' \
  https://api.parse.com/1/push

If we store relationships to other objects in our Installation class, we can also use those in our query. For example, we could send a push notification to all users near a given location like this.

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "where": {
         "user": {
           "$inQuery": {
             "location": {
               "$nearSphere": {
                 "__type": "GeoPoint",
                 "latitude": 30.0,
                 "longitude": -20.0
               },
               "$maxDistanceInMiles": 1.0
             }
           }
         }
       },
       "data": {
         "alert": "Free hotdogs at the Parse concession stand!"
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "where": {
          "user": {
            "$inQuery": {
              "location": {
                "$nearSphere": {
                  "__type": "GeoPoint",
                  "latitude": 30.0,
                  "longitude": -20.0
                },
                "$maxDistanceInMiles": 1.0
              }
            }
          }
        },
        "data": {
          "alert": "Free hotdogs at the Parse concession stand!"
        }
      }' \
  https://api.parse.com/1/push

An in depth look at the Installation end point can be found in the REST guide.

Sending Options

Push notifications can do more than just send a message. In iOS, pushes can also include the sound to be played, the badge number to display as well as any custom data you wish to send. In Android, it is even possible to specify an Intent to be fired upon receipt of a notification. An expiration date can also be set for the notification in case it is time sensitive.

Customizing your Notifications

If you want to send more than just a message, you can set other fields in the data dictionary. There are some reserved fields that have a special meaning.

  • alert: the notification's message.
  • badge: (iOS only) the value indicated in the top right corner of the app icon. This can be set to a value or to Increment in order to increment the current value by 1.
  • sound: (iOS only) the name of a sound file in the application bundle.
  • content-available: (iOS only) If you are a writing a Newsstand app, or an app using the Remote Notification Background Mode introduced in iOS7 (a.k.a. "Background Push"), set this value to 1 to trigger a background download.
  • category: (iOS only) the identifier of the UIUserNotificationCategory for this push notification.
  • uri: (Android only) an optional field that contains a URI. When the notification is opened, an Activity associated with opening the URI is launched.
  • title: (Android only) the value displayed in the Android system tray notification.
For example, to send a notification that increases the current badge number by 1 and plays a custom sound for iOS devices, and displays a particular title for Android users, you can do the following:

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "channels": [
         "Mets"
       ],
       "data": {
         "alert": "The Mets scored! The game is now tied 1-1.",
         "badge": "Increment",
         "sound": "cheering.caf",
         "title": "Mets Score!"
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "channels": [
          "Mets"
        ],
        "data": {
          "alert": "The Mets scored! The game is now tied 1-1.",
          "badge": "Increment",
          "sound": "cheering.caf",
          "title": "Mets Score!"
        }
      }' \
  https://api.parse.com/1/push

It is also possible to specify your own data in this dictionary. As explained in the Receiving Notifications section for iOS and Android, iOS will give you access to this data only when the user opens your app via the notification and Android will provide you this data in the Intent if one is specified.

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "channels": [
         "Indians"
       ],
       "data": {
         "action": "com.example.UPDATE_STATUS",
         "alert": "Ricky Vaughn was injured during the game last night!",
         "name": "Vaughn",
         "newsItem": "Man bites dog"
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "channels": [
          "Indians"
        ],
        "data": {
          "action": "com.example.UPDATE_STATUS",
          "alert": "Ricky Vaughn was injured during the game last night!",
          "name": "Vaughn",
          "newsItem": "Man bites dog"
        }
      }' \
  https://api.parse.com/1/push

Setting an Expiration Date

When a user's device is turned off or not connected to the internet, push notifications cannot be delivered. If you have a time sensitive notification that is not worth delivering late, you can set an expiration date. This avoids needlessly alerting users of information that may no longer be relevant.

There are two parameters provided by Parse to allow setting an expiration date for your notification. The first is expiration_time which takes a date (in ISO 8601 format or Unix epoch time) specifying when Parse should stop trying to send the notification. To expire the notification exactly 1 week from now, you can use the following command.

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "expiration_time": "2015-05-03T20:56:41Z",
       "data": {
         "alert": "Season tickets on sale until May  3, 2015"
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "expiration_time": "2015-05-03T20:56:41Z",
        "data": {
          "alert": "Season tickets on sale until May  3, 2015"
        }
      }' \
  https://api.parse.com/1/push

Alternatively, you can use the expiration_interval parameter to specify a duration of time before your notification expired. This value is relative to the push_time parameter used to schedule notifications. This means that a push notification scheduled to be sent out in 1 day and an expiration interval of 6 days can be received up to a week from now.

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "push_time": "2015-04-27T20:56:41Z",
       "expiration_interval": 518400,
       "data": {
         "alert": "Season tickets on sale until May  3, 2015"
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "push_time": "2015-04-27T20:56:41Z",
        "expiration_interval": 518400,
        "data": {
          "alert": "Season tickets on sale until May  3, 2015"
        }
      }' \
  https://api.parse.com/1/push

Targeting by Platform

If you build a cross platform app, it is possible you may only want to target iOS or Android devices. There are two methods provided to filter which of these devices are targeted. Note that both platforms are targeted by default.

The following examples would send a different notification to Android, iOS, and Windows users.

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "where": {
         "deviceType": "android"
       },
       "data": {
         "alert": "Your suitcase has been filled with tiny robots!"
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "where": {
          "deviceType": "android"
        },
        "data": {
          "alert": "Your suitcase has been filled with tiny robots!"
        }
      }' \
  https://api.parse.com/1/push
import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "where": {
         "deviceType": "ios"
       },
       "data": {
         "alert": "Your suitcase has been filled with tiny apples!"
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "where": {
          "deviceType": "ios"
        },
        "data": {
          "alert": "Your suitcase has been filled with tiny apples!"
        }
      }' \
  https://api.parse.com/1/push
import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "where": {
         "deviceType": "winrt"
       },
       "data": {
         "alert": "Your suitcase has been filled with tiny glass!"
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "where": {
          "deviceType": "winrt"
        },
        "data": {
          "alert": "Your suitcase has been filled with tiny glass!"
        }
      }' \
  https://api.parse.com/1/push
import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "where": {
         "deviceType": "winphone"
       },
       "data": {
         "alert": "Your suitcase is very hip; very metro."
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "where": {
          "deviceType": "winphone"
        },
        "data": {
          "alert": "Your suitcase is very hip; very metro."
        }
      }' \
  https://api.parse.com/1/push

Scheduling Pushes

You can schedule a push in advance by specifying a push_time. For example, if a user schedules a game reminder for a game on May 3, 2015 at noon UTC, you can schedule the push notification by sending:

import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/push', json.dumps({
       "where": {
         "user_id": "user_123"
       },
       "push_time": "2015-05-03T12:00:00Z",
       "data": {
         "alert": "You previously created a reminder for the game today"
       }
     }), {
       "X-Parse-Application-Id": "${APPLICATION_ID}",
       "X-Parse-REST-API-Key": "${REST_API_KEY}",
       "Content-Type": "application/json"
     })
result = json.loads(connection.getresponse().read())
print result
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "where": {
          "user_id": "user_123"
        },
        "push_time": "2015-05-03T12:00:00Z",
        "data": {
          "alert": "You previously created a reminder for the game today"
        }
      }' \
  https://api.parse.com/1/push

If you also specify an expiration_interval, it will be calculated from the scheduled push time, not from the time the push is submitted. This means a push scheduled to be sent in a week with an expiration interval of a day will expire 8 days after the request is sent.

The scheduled time cannot be in the past, and can be up to two weeks in the future. It can be an ISO 8601 date with a date, time, and timezone, as in the example above, or it can be a numeric value representing a UNIX epoch time in seconds (UTC). To schedule an alert for May 3, 2015 at noon UTC time, you can set the push_time to either 2015-05-03T12:00:00Z or 1430654400.

Local Push Scheduling

The push_time parameter can schedule a push to be delivered to each device according to its time zone. This technique delivers a push to all Installation objects with a timeZone member when that time zone would match the push time. For example, if an app had a device in timezone America/New_York and another in America/Los_Angeles, the first would receive the push three hours before the latter.

To schedule a push according to each device's local time, the timeZone parameter should be an ISO 8601 date without a time zone, i.e. 2015-05-03T12:00:00 . Note that Installations without a timeZone will be excluded from this localized push.

Receiving Pushes

Since push notifications are sent to clients, there is no REST API end point for receiving notifications. To learn more about handling received notifications in iOS or Android, use the platform toggle at the top.

Troubleshooting

For tips on troubleshooting push notifications, check the troubleshooting sections for iOS, Android, and .NET using the platform toggle at the top.

Push Notification Guide

Introduction

Push Notifications are a great way to keep your users engaged and informed about your app. You can reach your entire user base quickly and effectively. This guide will help you through the setup process and the general usage of Parse to send push notifications.

The PHP SDK does not currently support receiving pushes. It can only be used to send notifications to iOS and Android applications. A common use case is to send pushes from Cloud Code.

Setting Up Push

There is no setup required to use the PHP SDK for sending push notifications. If you haven't configured your iOS or Android clients to use Push, take a look at their respective setup instruction using the platform toggle at the top.

Installations

Every Parse application installed on a device registered for push notifications has an associated Installation object. The Installation object is where you store all the data needed to target push notifications. For example, in a baseball app, you could store the teams a user is interested in to send updates about their performance.

Note that Installation data can only be modified by the client SDKs, the data browser, or the REST API.

This class has several special fields that help you manage and target devices.

  • badge: The current value of the icon badge for iOS apps. Changes to this value on the server will be used for future badge-increment push notifications.
  • channels: An array of the channels to which a device is currently subscribed.
  • timeZone: The current time zone where the target device is located. This value is synchronized every time an Installation object is saved from the device (readonly).
  • deviceType: The type of device, "ios" or "android" (readonly).
  • installationId: Unique Id for the device used by Parse (readonly).
  • deviceToken: The Apple generated token used for iOS devices (readonly).

Sending Pushes

There are two ways to send push notifications using Parse: channels and advanced targeting. Channels offer a simple and easy to use model for sending pushes, while advanced targeting offers a more powerful and flexible model. Both are fully compatible with each other and will be covered in this section.

You can view your past push notifications on the Parse.com push console for up to 30 days after creating your push. For pushes scheduled in the future, you can delete the push on the web console as long as no sends have happened yet. After you send the push, the web console shows push analytics graphs.

Using Channels

The simplest way to start sending notifications is using channels. This allows you to use a publisher-subscriber model for sending pushes. Devices start by subscribing to one or more channels, and notifications can later be sent to these subscribers. The channels subscribed to by a given Installation are stored in the channels field of the Installation object.

Subscribing to Channels

The PHP SDK does not currently support subscribing iOS and Android devices for pushes. Take a look at the iOS, Android or REST Push guide using the platform toggle at the top.

Sending Pushes to Channels

With the PHP SDK, the following code can be used to alert all subscribers of the "Giants" and "Mets" channels about the results of the game. This will display a notification center alert to iOS users and a system tray notification to Android users.

$data = array("alert" => "Hi!");

ParsePush::send(array(
  "channels" => ["PHPFans"],
  "data" => $data
));

Using Advanced Targeting

While channels are great for many applications, sometimes you need more precision when targeting the recipients of your pushes. Parse allows you to write a query for any subset of your Installation objects using the querying API and to send them a push.

Since Installation objects are just like any other object stored in Parse, you can save any data you want and even create relationships between Installation objects and your other objects. This allows you to send pushes to a very customized and dynamic segment of your user base.

Saving Installation Data

The PHP SDK does not currently support modifying Installation objects. Take a look at the iOS, Android or REST Push guide using the platform toggle at the top.

Sending Pushes to Queries

Once you have your data stored on your Installation objects, you can use a query to target a subset of these devices. Parse.Installation queries work just like any other Parse query.

$query = ParseInstallation::query();
$query->equalTo("design", "rad");
ParsePush::send(array(
  "where" => $query,
  "data" => $data
));

We can even use channels with our query. To send a push to all subscribers of the "Giants" channel but filtered by those who want score update, we can do the following:

$query = ParseInstallation::query();
$query->equalTo("channels", "Giants");
$query->equalTo("scores", true);

ParsePush::send(array(
  "where" => $query,
  "data" => array(
    "alert" => "Giants scored against the A's! It's now 2-2."
  )
));

If we store relationships to other objects in our Installation class, we can also use those in our query. For example, we could send a push notification to all users near a given location like this.

// Find users near a given location
$userQuery = ParseUser::query();
$userQuery->withinMiles("location", $stadiumLocation, 1.0);

// Find devices associated with these users
$pushQuery = ParseInstallation::query();
$pushQuery->matchesQuery('user', $userQuery);

// Send push notification to query
ParsePush::send(array(
  "where" => $pushQuery,
  "data" => array(
    "alert" => "Free hotdogs at the Parse concession stand!"
  )
));

Sending Options

Push notifications can do more than just send a message. In iOS, pushes can also include the sound to be played, the badge number to display as well as any custom data you wish to send. In Android, it is even possible to specify an Intent to be fired upon receipt of a notification. An expiration date can also be set for the notification in case it is time sensitive.

Customizing your Notifications

If you want to send more than just a message, you can set other fields in the data dictionary. There are some reserved fields that have a special meaning.

  • alert: the notification's message.
  • badge: (iOS only) the value indicated in the top right corner of the app icon. This can be set to a value or to Increment in order to increment the current value by 1.
  • sound: (iOS only) the name of a sound file in the application bundle.
  • content-available: (iOS only) If you are a writing a Newsstand app, or an app using the Remote Notification Background Mode introduced in iOS7 (a.k.a. "Background Push"), set this value to 1 to trigger a background download.
  • category: (iOS only) the identifier of the UIUserNotificationCategory for this push notification.
  • uri: (Android only) an optional field that contains a URI. When the notification is opened, an Activity associated with opening the URI is launched.
  • title: (Android only) the value displayed in the Android system tray notification.

For example, to send a notification that increases the current badge number by 1 and plays a custom sound for iOS devices, and displays a particular title for Android users, you can do the following:

ParsePush::send(array(
  "channels" => [ "Mets" ],
  data => array(
    "alert" => "The Mets scored! The game is now tied 1-1.",
    "badge" => "Increment",
    "sound" => "cheering.caf",
    "title" => "Mets Score!"
  )
));

It is also possible to specify your own data in this dictionary. As explained in the Receiving Notifications section for iOS and Android, iOS will give you access to this data only when the user opens your app via the notification and Android will provide you this data in the Intent if one is specified.

$query = ParseInstallation::query();
$query->equalTo('channels', 'Indians');
$query->equalTo('injuryReports', true);

ParsePush::send(array(
  "where" => $query,
  "data" => array(
    "action" => "com.example.UPDATE_STATUS"
    "alert" => "Ricky Vaughn was injured in last night's game!",
    "name" => "Vaughn",
    "newsItem" => "Man bites dog"
  )
));

Setting an Expiration Date

When a user's device is turned off or not connected to the internet, push notifications cannot be delivered. If you have a time sensitive notification that is not worth delivering late, you can set an expiration date. This avoids needlessly alerting users of information that may no longer be relevant.

There are two parameters provided by Parse to allow setting an expiration date for your notification. The first is expiration_time which takes a DateTime specifying when Parse should stop trying to send the notification.

Alternatively, you can use the expiration_interval parameter to specify a duration of time before your notification expires. This value is relative to the push_time parameter used to schedule notifications. This means that a push notification scheduled to be sent out in 1 day and an expiration interval of 6 days can be received up to a week from now.

Targeting by Platform

If you build a cross platform app, it is possible you may only want to target iOS or Android devices. There are two methods provided to filter which of these devices are targeted. Note that both platforms are targeted by default.

The following examples would send a different notification to Android and iOS users.

// Notification for Android users
$queryAndroid = ParseInstallation::query();
$queryAndroid->equalTo('deviceType', 'android');

ParsePush::send(array(
  "where" => $queryAndroid,
  "data" => array(
    "alert" => "Your suitcase has been filled with tiny robots!" 
  )
));

// Notification for iOS users
$queryIOS = ParseInstallation::query();
$queryIOS->equalTo('deviceType', 'ios');

ParsePush::send(array(
  "where" => $queryIOS,
  "data" => array(
    "alert" => "Your suitcase has been filled with tiny apples!" 
  )
));

// Notification for Windows 8 users
$queryWindows = ParseInstallation::query();
$queryWindows->equalTo('deviceType', 'winrt');

ParsePush::send(array(
  "where" => $queryWindows,
  "data" => array(
    "alert" => "Your suitcase has been filled with tiny surfaces!" 
  )
));

// Notification for Windows Phone 8 users
$queryWP8 = ParseInstallation::query();
$queryWP8->equalTo('deviceType', 'winphone');

ParsePush::send(array(
  "where" => $queryWP8,
  "data" => array(
    "alert" => "Your suitcase is very hip; very metro." 
  )
));

Scheduling Pushes

You can schedule a push in advance by specifying a push_time parameter of type DateTime.

If you also specify an expiration_interval, it will be calculated from the scheduled push time, not from the time the push is submitted. This means a push scheduled to be sent in a week with an expiration interval of a day will expire 8 days after the request is sent.

The scheduled time cannot be in the past, and can be up to two weeks in the future. It can be an ISO 8601 date with a date, time, and timezone, as in the example above, or it can be a numeric value representing a UNIX epoch time in seconds (UTC).

Receiving Pushes

The PHP SDK does not currently support receiving pushes. To learn more about handling received notifications in iOS or Android, use the platform toggle at the top.

Troubleshooting

For tips on troubleshooting push notifications, check the troubleshooting sections for iOS, Android, and .NET using the platform toggle at the top.