Set up server for push notifications

Google Play generates notifications in response to events that occur and can affect an enterprise. EMM solution providers can receive these notifications and act upon them, by providing alerts or other mechanisms for their customer administrators, for example.

This guide tells you how to configure the server infrastructure needed to receive and process EMM push notifications only. Pull notifications do not require the setup detailed in this guide.

In addition to the server setup described in this guide, for push notifications you must also grant the appropriate privileges and perform other configuration tasks in the Google API Console. See Enable EMM push notifications for details.

For more information about Google Cloud Pub/Sub, including examples, see Cloud Pub/Sub documentation.

You can configure your system so that push notifications are sent to a specified HTTPS endpoint, or to a server that waits for notifications to be sent.

About push endpoint configuration

To configure a push endpoint you need a server with a valid SSL certificate. In this example, you create and upload an SSL certificate to a certificate authority (CA), then configure the NGINX server. Finally you compile and run test code to confirm that your setup is correct.

This example shows how to configure an NGINX server in reverse proxy mode to connect to the subscriber app (in PushSubscriber.java) running on port 8093, using Ubuntu 14.04. Your enterprise might use a different server, but the sample setup should work, without changes, on all Debian-based distributions. Other distributions (such as those based on RedHat) are similar, but the location of the configuration files may be different.

Before you can receive notifications, you must configure an endpoint that meets the following criteria:

  • You must own the domain and verify your ownership in the Google API Console.

  • You must be able to run a service on port 443 (SSL).

  • You must have a CA-signed SSL certificate. Self-signed certificates do not work.

  • The web server you are running must support webhooks.

Your endpoint does not need to run on Google App Engine (although it can).

Create and upload an SSL certificate

1. Produce a Secure Sockets Layer (SSL) certificate:

    myusername@myhost:/tmp$ sudo openssl req -x509 -nodes -days 365 \
      -newkey rsa:2048 -keyout cert.key -out cert.crt

This generates the following response. Replace the sample values (such as push.solarmora.com and myusername@myhost) with your actual server name, company, address, and so on, in the following code. You can use any subdomain as long as the A record of this subdomain points to your server.

    Generating a 2048 bit RSA private key
    ...........................................................................
    .....+++
    writing new private key to 'cert.key'
    -----
    You are about to be asked to enter information that will be incorporated
    into your certificate request.
    What you are about to enter is what is called a Distinguished Name or a DN.
    There are quite a few fields but you can leave some blank
    For some fields there will be a default value,
    If you enter '.', the field will be left blank.
    -----
    Country Name (2 letter code) [AU]:GB
    State or Province Name (full name) [Some-State]:England
    Locality Name (eg, city) []:London
    Organization Name (eg, company) [Internet Widgits Pty Ltd]:Solarmora, Inc.
    Organizational Unit Name (eg, section) []:Creative Publications
    Common Name (e.g. server FQDN or YOUR name) []:push.solarmora.com
    Email Address []:admin@solarmora.com

2. Verify that a certificate file was created:

$ myusername@myhost:/tmp$ ls cert*
cert.crt  cert.key

3. To get this certificate signed, produce a certificate signing request (CSR) to upload to your signer:

myusername@myhost:/tmp$ sudo openssl x509 -x509toreq -in cert.crt \
  -out cert.csr -signkey cert.key
Getting request Private Key
Generating certificate request
myusername@myhost:/tmp$ ls cert.*
cert.crt  cert.csr  cert.key

4. Ensure that the content of the CSR file looks like this:

    Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=GB, ST=England, L=London, O=Solarmora, Inc.,
        OU=Creative Publications,
        CN=push.solarmora.com/emailAddress=admin@solarmora.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:cc:0f:54:26:3d:d9:17:eb:8f:6c:f7:27:5e:77:
                    64:65:00:db:fe:2a:1f:fa:ea:de:21:7a:c5:5d:87:
                    ...
                    ...
                Exponent: 65537 (0x10001)
        Attributes:
            a0:00
    Signature Algorithm: sha256WithRSAEncryption
         1d:ea:12:b8:c2:6a:d6:f4:6e:92:2f:b9:12:5e:e3:91:15:a0:
         06:b5:81:ce:c5:cf:b7:d2:a7:dd:f2:78:ca:28:8e:21:cd:6d:
         ...
         ...
    -----BEGIN CERTIFICATE REQUEST-----
    MIIC6zCCAdMCAQAwgaUxCzAJBgNVBAYTAkdCMRAwDgYDVQQIDAdFbmdsYW5kMQ8w
    DQYDVQQHDAZMb25kb24xGDAWBgNVBAoMD0FDTUUgQ29ycCwgSW5jLjEYMBYGA1UE
    CwwPQ3JlYXRpdmUgQW52aWxzMRswGQYDVQQDDBJwdXNoLmFjbWUtY29ycC5jb20x
    IjAgBgkqhkiG9w0BCQEWE2FkbWluQGFjbWUtY29ycC5jb20wggEiMA0GCSqGSIb3
    ...
    ...
    -----END CERTIFICATE REQUEST-----

5. Upload the part of your certificate between the BEGIN and END lines (inclusive) to your CA. The exact process will depend on your CA, but will include the following steps:

  1. Upload your CSR file to your CA site, or paste the content of your file onto your CA site. Your CA then validates and processes this data.
  2. Download the signed certificate generated by your CA.

6. The output from the CA should contains multiple files: the signed certificate itself and the CA's certificate confirming they are eligible to sign certificates. Concatenate all *.crt certificate files in the downloaded bundle into a single bundle file, for example bundle.push.solarmora.com.crt:

$ cat *.crt > bundle.push.solarmora.com.crt

Configure your proxy server

In this section you configure the NGINX open source web server and reverse proxy server to serve the endpoint and forward all incoming requests to the subscriber server. NGINX is used as an example, but you can use any other HTTP proxy instead.

1. Install NGINX on your server:

    $ sudo apt-get update
    $ sudo apt-get install nginx
    $ nginx -v
    $ nginx version: nginx/1.4.6 (Ubuntu)

2. To ensure that the extra server conifguration files you create in the sites-enabled directory are processed by NGINX, edit /etc/nginx/nginx.conf and include the following:

    $ include /etc/nginx/conf.d/*.conf;
    $ include /etc/nginx/sites-enabled/*;

3. Copy your certificate files to a safe location, readable by the www-data user, but preferably not readable by any other user (you may need to adjust the user name if your web server is running as a different user):

    $ sudo mkdir -p /var/openssl/push.solarmora.com
    $ sudo mv /tmp/cert.key 
/var/openssl/push.solarmora.com/push.solarmora.com.key $ sudo mv /tmp/bundle.push.solarmora.com.crt
/var/openssl/push.solarmora.com/bundle.push.solarmora.com.crt

4. Create a new server configuration. Edit push.solarmora.com in /etc/nginx/sites-enabled and use your actual server's fully qualified domain name as the file name:

server {
   listen 443;
   server_name push.solarmora.com;

   ssl on;
   ssl_certificate
     /var/openssl/push.solarmora.com/bundle.push.solarmora.com.crt;
   ssl_certificate_key
     /var/openssl/push.solarmora.com/push.solarmora.com.key;

   # it is usually very convenient to have separate files for your
   # access and error log to analyze for possible problems
   access_log /var/log/nginx/nginx.push.solarmora.com.log;
   error_log /var/log/nginx/nginx.push.solarmora.com.log;

   location / {
            # assuming the subscriber will run on the same machine
            # on port 8093
            proxy_pass http://localhost:8093;
   }
}

5. Restart NGINX to implement the changes:

    myusername@myhost:/etc/nginx$ sudo service nginx restart
    * Restarting nginx nginx
    ...done.

6. Your server is now configured. To verify the configuration, try to query your server using curl:

    [myusername@myhost:~]$ curl push.solarmora.com
    <html>
    <head><title>502 Bad Gateway</title></head>
    <body bgcolor="white">
    <center><h1>502 Bad Gateway</h1></center>
    <hr><center>nginx/1.4.6 (Ubuntu)</center>
    </body>
    </html>

This is the expected response given that no downstream server has been configured (localhost:8093 in our config file).

Compile and run examples

To run the examples in this section you need an active Google API Console project. We recommend you create one specifically for testing purposes and keep it separate from your production project. After you create a test project, you need to create a service account. Make a note of the service account email address, and put the associated .p12 file somewhere on your server.

Set up the source code tree

1. Clone the play-work.git repository:

    myusername@myhost:~/code$ git clone
    https://github.com/google/play-work.git
    Cloning into 'play-work'...
    Username for 'https://github.com': username
    Password for 'https://username@github.com':
    remote: Counting objects: 110, done.
    remote: Compressing objects: 100% (60/60), done.
    remote: Total 110 (delta 24), reused 95 (delta 9), pack-reused 0
    Receiving objects: 100% (110/110), 23.88 KiB | 0 bytes/s, done.
    Resolving deltas: 100% (24/24), done.
    Checking connectivity... done.
2. On Debian-based systems install both Maven and the Google Protocol Buffers compiler:
    $ sudo apt-get install maven protobuf-compiler

3. Verify that both Maven and the Google Protocol Buffers compiler are installed correctly:

    myusername@myhost:~$ mvn -v
    Apache Maven 3.0.5
    Maven home: /usr/share/maven
    Java version: 1.7.0_75, vendor: Oracle Corporation
    Java home: /usr/lib/jvm/java-7-openjdk-amd64/jre
    Default locale: en_US, platform encoding: UTF-8
    OS name: "linux", version: "3.16.0-30-generic", arch: "amd64", family: "unix"
    myusername@myhost:~$ protoc --version
    libprotoc 2.5.0

4. The Maven configuration file pom.xml assumes that the Protocol Buffers compiler is installed to the /usr/bin/protoc directory:

    myusername@myhost:~$ which protoc
    /usr/bin/protoc
If this is not the case, you can either modify pom.xml or symlink protoc:
    $ sudo ln -s which protoc /usr/bin/protoc
5. Compile the examples. Verify that you can build the code by running mvn clean compile assembly:single. This should produce a file named emm-notifications-[version-number]-jar-with-dependencies.jar, where [version number] is the current version of the example, for example 1.0-SNAPSHOT:
    myusername@myhost:~/code/play-work/examples/emm-notifications$ ls target/*
    target/emm-notifications-1.0-SNAPSHOT-jar-with-dependencies.jar
6. Verify that you can run the compiled TestPublisher code. It is expected that the code will fail:

    myusername@myhost:~/code/play-work/examples/emm-notifications$ java -cp \
      target/emm-notifications-1.0-SNAPSHOT-jar-with-dependencies.jar \
      com.google.android.work.emmnotifications.TestPublisher
    Exception in thread "main" java.lang.IllegalArgumentException:
    You must specify non-default ServiceAccountEmail in
    settings.properties
        at com.google.api.client.repackaged.com.google.common.base.Preconditions.checkArgument(Preconditions.java:119)
        at com.google.api.client.util.Preconditions.checkArgument(Preconditions.java:69)
        at com.google.android.work.emmnotifications.Settings.verifyVariable(Settings.java:129)
        at com.google.android.work.emmnotifications.Settings.getSettings(Settings.java:103)
        at com.google.android.work.emmnotifications.TestPublisher.main(TestPublisher.java:39)

7. You must override some values in the settings.properties file. To do this, create a copy of the file and modify the properties in the copy as follows:

    # This should be your own service account's email address
    ServiceAccountEmail=368628613713-t4hfexampledn5lhpdcu1qqfgio01626@developer.gserviceaccount.com
    ServiceAccountP12KeyFile=/opt/secret/secret.p12
    # This will be the name of the service account
    ProjectName=enterprise-cloud-pub-sub
    SubscriptionName=projects/enterprise-cloud-pub-sub/subscriptions/default
    TopicName=projects/enterprise-cloud-pub-sub/topics/default
    # The push endpoint in your API Console project
    PushEndpoint=https://push.solarmora.com

8. Run the TestPublisher code again to make sure it no longer crashes. (You may see a single error in the log output.)

Run the publisher test code

You need to run the sample code for publishing notifications so that your subscriber has some messages to read.

In the following example, the code looks for the topic specified in my_settings.properties but doesn't find it, and therefore it creates the topic. It then publishes a message to the topic. This example provides a valuable testing tool that allows you to emulate messages sent by the Google Play EMM API.

    myusername@myhost:~/code/play-work/examples/emm-notifications$ DEVELOPER_CONSOLE_SETTINGS=./my_settings.properties java -cp \
      target/emm-notifications-1.0-SNAPSHOT-jar-with-dependencies.jar com.google.android.work.emmnotifications.TestPublisher
    Feb 27, 2015 1:39:59 PM com.google.android.work.emmnotifications.RetryHttpInitializerWrapper$1 handleResponse
    INFO: RetryHandler: {
      "error": {
        "code": 404,
        "message": "Resource not found (resource=default).",
        "errors": [
          {
            "message": "Resource not found (resource=default).",
            "domain": "global",
            "reason": "notFound"
          }
        ],
        "status": "NOT_FOUND"
      }
    }

    Feb 27, 2015 1:39:59 PM com.google.android.work.emmnotifications.TestPublisher main
    INFO: Topic projects/enterprise-cloud-pub-sub/topics/default doesn't exists, creating it
    Feb 27, 2015 1:40:02 PM com.google.android.work.emmnotifications.TestPublisher main
    INFO: Topic projects/enterprise-cloud-pub-sub/topics/default created
    Feb 27, 2015 1:40:02 PM com.google.android.work.emmnotifications.TestPublisher main
    INFO: Publishing a request: {messages=[{data=CjEKFQoIMTIzMjEzMjESCXJpZ2h0IG5vdxIWY29tLmdvb2dsZS5hbmRyb2lkLmdtcxgA}]}

Run the subscriber test code

The subscriber test code confirms that you can receive the messages published by the TestPublisher code.

1. Ensure that your code is up to date and compiled, and then run the subscriber test code:

    myusername@myhost:~/code/play-work/examples/emm-notifications$ DEVELOPER_CONSOLE_SETTINGS=./my_settings.properties 
java -cp target/emm-notifications-1.0-SNAPSHOT-jar-with-dependencies.jar
com.google.android.work.emmnotifications.PushSubscriber Feb 27, 2015 1:46:37 PM com.google.android.work.emmnotifications.PushSubscriber main INFO: Will be using topic name: projects/enterprise-cloud-pub-sub/topics/default, subscription name:
projects/enterprise-cloud-pub-sub/subscriptions/default Feb 27, 2015 1:46:38 PM com.google.android.work.emmnotifications.PushSubscriber main INFO: Trying to get subscription named projects/enterprise-cloud-pub-sub/subscriptions/default Feb 27, 2015 1:46:38 PM com.google.android.work.emmnotifications.RetryHttpInitializerWrapper$1 handleResponse INFO: RetryHandler: { "error": { "code": 404, "message": "Resource not found (resource=default).", "errors": [ { "message": "Resource not found (resource=default).", "domain": "global", "reason": "notFound" } ], "status": "NOT_FOUND" } } Feb 27, 2015 1:46:38 PM com.google.android.work.emmnotifications.PushSubscriber main INFO: Subscription doesn't exist, will try to create projects/enterprise-cloud-pub-sub/subscriptions/default Feb 27, 2015 1:46:43 PM com.google.android.work.emmnotifications.PushSubscriber main INFO: Created: { "ackDeadlineSeconds" : 600, "name" : "projects/enterprise-cloud-pub-sub/subscriptions/default", "pushConfig" : { "pushEndpoint" : "https://push.solarmora.com" }, "topic" : "projects/enterprise-cloud-pub-sub/topics/default" }
The subscriber is now running and ready to accept incoming messages.

2. Run the publisher again, and new messages are added to the log:

    Feb 27, 2015 1:47:24 PM com.google.android.work.emmnotifications.PushSubscriber$1 handle
    INFO: Raw request: {"message":{"data":"CjEKFQoIMTIzMjEzMjESCXJpZ2h0IG5vdxIWY29tLmdvb2dsZS5hbmRyb2lkLmdtcxgA",
"attributes":{},"message_id":"71571141246"},"subscription":"/subscriptions/enterprise-cloud-pub-sub/default"} Feb 27, 2015 1:47:24 PM com.google.android.work.emmnotifications.PushSubscriber$1 handle INFO: Pubsub message received: { "attributes" : { }, "data" : "CjEKFQoIMTIzMjEzMjESCXJpZ2h0IG5vdxIWY29tLmdvb2dsZS5hbmRyb2lkLmdtcxgA", "message_id" : "71571141246" } Feb 27, 2015 1:47:24 PM com.google.android.work.emmnotifications.PushSubscriber$1 handle INFO: Message received: product_approval_event { common_event_information { enterprise_id: "12321321" event_notification_sent_timestamp: "right now" } product_id: "com.google.android.gms" approved: false }
A message has been properly received and processed.