Push Service Design

From the WebPush specification:

A general model for push services includes three basic actors: a user agent, a push service, and an application (server).

    +-------+           +--------------+       +-------------+
    |  UA   |           | Push Service |       | Application |
    +-------+           +--------------+       +-------------+
        |                      |                      |
        |      Subscribe       |                      |
        |--------------------->|                      |
        |       Monitor        |                      |
        |<====================>|                      |
        |                      |                      |
        |          Distribute Push Resource           |
        |-------------------------------------------->|
        |                      |                      |
        :                      :                      :
        |                      |     Push Message     |
        |    Push Message      |<---------------------|
        |<---------------------|                      |
        |                      |                      |

In WebPush, the Push Message is used to wake UA code. Firefox OS uses the the WebPush API for its UA subscription API.

Currently the Mozilla Push Service supports a proprietary protocol using a websocket between the UA and the Push Service.

WebPush Proprietary Protocol

This documents the current Push Service Protocol used between the UA and the Push Service.

The protocol uses a websocket that carries JSON messages in a mostly request/response style format. The only exception is that after the Hello response the Push Server will send any stored notifications as well as new notifications when they arrive.

All websocket connections are secured with TLS (wss://). The Push Server maintains a list of all previously seen UAID's and notifications for UA's that came in while the UA was disconnected. There is no authentication for a given UAID when reconnecting.

It is required that ChannelID's and UAID's be UUID's.

Messages

All messages are encoded as JSON. All messages MUST have the following fields:

messageType string
Defines the message type

Handshake

After the WebSocket is established, the UserAgent begins communication by sending a hello message. The hello message contains the UAID if the UserAgent has one, either generated by the UserAgent for the first handshake or returned by the server from an earlier handshake. The UserAgent also transmits the channelIDs it knows so the server may synchronize its state.

The server MAY respect this UAID, but it is at liberty to ask the UserAgent to change its UAID in the response.

If the UserAgent receives a new UAID, it MUST delete all existing channelIDs and their associated versions. It MAY then wake up all registered applications immediately or at a later date by sending them a push-register message.

A repeated hello message on an established channel may result in the server disconnecting the client for bad behavior.

The handshake is considered complete, once the UserAgent has received a reply.

An UserAgent MUST transmit a hello message only once on its WebSocket. If the handshake is not completed in the first try, it MUST disconnect the WebSocket and begin a new connection.

NOTE: Applications may request registrations or unregistrations from the UserAgent, before or when the handshake is in progress. The UserAgent MAY buffer these or report errors to the application. But it MUST NOT send these requests to the PushServer until the handshake is completed.

UserAgent -> PushServer

messageType = "hello"
Begin handshake
uaid string REQUIRED
If the UserAgent has a previously assigned UAID, it should send it. Otherwise send an empty string.
channelIDs list of strings REQUIRED
If the UserAgent has a list of channelIDs it wants to be notified of, it must pass these, otherwise an empty list.
use_webpush bool OPTIONAL
If the UserAgent wants the WebPush data support for notifications it may include this field with a value of true. This field may appear for legacy reasons.

Extra fields: The UserAgent MAY pass any extra JSON data to the PushServer. This data may include information required to wake up the UserAgent out-of-band. The PushServer MAY ignore this data.

Example

{
  "messageType": "hello",
  "uaid": "fd52438f-1c49-41e0-a2e4-98e49833cc9c"
}

Register

The Register message is used by the UserAgent to request that the PushServer notify it when a channel changes. Since channelIDs are associated with only one UAID, this effectively creates the channel, while unregister destroys the channel.

The channelID is chosen by the UserAgent because it also acts like a nonce for the Register message itself. Because of this PushServers MAY respond out of order to multiple register messages or messages may be lost without compromising correctness of the protocol.

The request is considered successful only after a response is received with a status code of 200. On success the UserAgent MUST:

  • Update its persistent storage based on the response
  • Notify the application of a successful registration.
  • On error, the UserAgent MUST notify the application as soon as possible.

NOTE: The register call is made by the UserAgent on behalf of an application. The UserAgent SHOULD have reasonable timeouts in place so that the application is not kept waiting for too long if the server does not respond or the UserAgent has to retry the connection.

UserAgent -> PushServer

messageType = "register"

channelID string REQUIRED
A unique identifier generated by the UserAgent, distinct from any existing channelIDs it has registered. It is RECOMMENDED that this is a UUIDv4 token.

Example

{
    "messageType": "register",
    "channelID": "d9b74644-4f97-46aa-b8fa-9393985cd6cd"
}

PushServer -> UserAgent

messageType = "register"

channelID string REQUIRED
This MUST be the same as the channelID sent by the UserAgent in the register request that this message is a response to.
status number REQUIRED
Used to indicate success/failure. MUST be one of:
  • 200 - OK. Success. Idempotent: If the PushServer receives a register for the same channelID from a UserAgent which already has a registration for the channelID, it should still respond with success.

  • 409 - Conflict. The chosen ChannelID is already in use and NOT associated with this UserAgent. UserAgent SHOULD retry with a new ChannelID as soon as possible.

  • 500 - Internal server error. Database out of space or offline. Disk space full or whatever other reason due to which the PushServer could not grant this registration. UserAgent SHOULD avoid retrying immediately.

pushEndpoint string REQUIRED
Should be the URL sent to the application by the UserAgent. AppServers will contact the PushServer at this URL to update the version of the channel identified by channelID.

Example

 {
   "messageType": "register",
   "channelID": "d9b74644-4f97-46aa-b8fa-9393985cd6cd",
   "status": 200,
   "pushEndpoint": "http://pushserver.example.org/d9b74644"
 }

Unregister

Unregistration is required for the WebPush.

PushServers MUST support it. UserAgents SHOULD support it.

The unregister is required only between the App and the UserAgent, so that the UserAgent stops notifying the App when the App is no longer interested in a pushEndpoint.

The unregister is also useful to the AppServer, because it should stop sending notifications to an endpoint the App is no longer monitoring. Even then, it is really an optimization so that the AppServer need not have some sort of garbage collection mechanism to clean up endpoints at intervals of time.

The PushServer MUST implement unregister, but need not rely on it. Well behaved AppServers will stop notifying it of unregistered endpoints automatically. Well behaved UserAgents won't notify their apps of unregistered updates either. So the PushServer can continue to process notifications and pass them on to UserAgents, when it has not been told about the unregistration.

When an App calls unregister(endpoint) it is RECOMMENDED that the UserAgent follow these steps:

  1. Remove its local registration first, for example from the database. This will allow it to immediately start ignoring updates.
  2. Notify the App that unregistration succeeded.
  3. Fire off an unregister message to the PushServer

UserAgent -> PushServer

messageType = "unregister"

channelID string REQUIRED
ChannelID that should be unregistered.

Example

{
    "messageType": "unregister",
    "channelID": "d9b74644-4f97-46aa-b8fa-9393985cd6cd"
}

PushServer -> UserAgent

messageType = "unregister"

channelID string REQUIRED
This MUST be the same as the channelID sent by the UserAgent in the unregister request that this message is a response to.
status number REQUIRED
Used to indicate success/failure. MUST be one of:
  • 200 - OK. Success. Idempotent: If the PushServer receives a unregister for a non-existent channelID it should respond with success. If the channelID is associated with a DIFFERENT UAID, it MUST NOT delete the channelID, but still MUST respond with success to this UserAgent.

  • 500 - Internal server error. Database offline or whatever other reason due to which the PushServer could not grant this unregistration. UserAgent SHOULD avoid retrying immediately.

Example

{
    "messageType": "unregister",
    "channelID": "d9b74644-4f97-46aa-b8fa-9393985cd6cd",
    "status": 200
}

Ping

The connection to the Push Server may be lost due to network issues. When the UserAgent detects loss of network, it should reconnect. There are situations in which the TCP connection dies without either end discovering it immediately. The UserAgent MAY send a ping approximately every 30 minutes and expect a reply from the server in a reasonable time (The Mozilla UserAgent uses 10 seconds). If no data is received, the connection should be presumed closed and a new connection started.

A UserAgent MUST NOT send a Ping more frequently than once a minute or its connection MAY be dropped.

The UserAgent should consider normal communications as an indication that the socket is working properly. It SHOULD send the ping packet only if no activity has occurred in the past 30 minutes.

Note: This section is included for relevant historical purposes as the current implementation sends WebSocket Ping frames every 5 minutes which is sufficient to keep the connection alive. As such, client-sent Ping's are no longer needed.

UserAgent -> PushServer

The 2-character string {} is sent. This is a valid JSON object that requires no alternative processing on the server, while keeping transmission size small.

PushServer -> UserAgent

The PushServer may reply with any data. The UserAgent is only concerned about the state of the connection. The PushServer may deliver pending notifications or other information. If there is no pending information to be sent, it is RECOMMENDED that the PushServer also reply with the string {}.

Notification

AppServer -> PushServer

The AppServer MUST make a HTTP PUT request to the Endpoint received from the App, or a HTTP POST if using the WebPush extension.

If no request body is present, the server MAY presume the version to be the current server UTC.

If the request body is present, the request MUST contain the string "version=N" and the Content-Type MUST be application/x-www-form-urlencoded.

PushServer -> AppServer

The HTTP response status code indicates if the request was successful.

  • 200 - OK. The PushServer will attempt to deliver a notification to the associated UserAgent.

  • 500 - Something went wrong with the server. Rare, but the AppServer should try again.

The HTTP response body SHOULD be empty.

PushServer -> UserAgent

Notifications are acknowledged by the UserAgent. PushServers should retry unacknowledged notifications every 60 seconds. If the version of an unacknowledged notification is updated, the PushServer MAY queue up a new notification for this channelID and the new version, and remove the old notification from the pending queue.

messageType = "notification"

The following attributes are present in each notification. A notification message is sent for every stored notification individually, as well as new notifications.

channelID string REQUIRED
ChannelID of the notification
version string REQUIRED
Version of the notification sent
headers map OPTIONAL
Encryption headers sent along with the data. Present only if data was sent.
data string OPTIONAL
Data payload, if included. This string is Base64 URL-encoded.

Example

{
    "messageType": "notification",
    "channelID": "431b4391-c78f-429a-a134-f890b5adc0bb",
    "version": "a7695fa0-9623-4890-9c08-cce0231e4b36:d9b74644-4f97-46aa-b8fa-9393985cd6cd"
}

UserAgent -> PushServer

It is RECOMMENDED that the UserAgent try to batch all pending acknowledgements into fewer messages if bandwidth is a concern. The ack MUST be sent as soon as the message has been processed, otherwise the Push Server MAY cease sending notifications to avoid holding excessive client state.

messageType = "ack"

updates list
The list contains one or more {"channelID": channelID, "version": N} pairs.

Example

{
    "messageType": "ack",
    "updates": [{ "channelID": "431b4391-c78f-429a-a134-f890b5adc0bb", "version": 23 }, { "channelID": "a7695fa0-9623-4890-9c08-cce0231e4b36", "version": 42 } ]
}