Push Notifications
A device that is not connected learns about new messages from a push. Push registrations belong to persistent sessions: a device registers the token its push service issued it, and the server wakes the device whenever its session has no active connection.
A push is a signal first. The device connects, catches up and shows notifications as it always does. A push also carries the start of the message, so that a device that cannot reach the server in time can still say who wrote and what. Messages are stored whether or not a push is delivered, so a lost or late push delays a notification and loses nothing.
A session registered for pushes keeps its user online after its connections
close: the device can still be reached, and the status the user chose stays as
it is. The user goes offline once no connection is open and no session is
registered — after push.unregister, logout, revocation or expiry, or once the
provider reports the device gone.
Firebase Cloud Messaging is the only provider, fcm. Only a server holding
credentials for the Firebase project an app is built with can wake that app's
devices. A server without them leaves push.register and push.unregister out
of connection.hello and answers them with not_supported, and its clients
learn about messages only while connected.
Register a Device
{"id":"push-1","type":"push.register","data":{"provider":"fcm","token":"dQw4w9WgXcQ:APA91bH..."}}
{"id":"push-1","type":"push.register","ok":true}
| Field | Type | Required | Description |
|---|---|---|---|
provider |
string | Yes | fcm |
token |
string | Yes | The registration token the provider issued the app |
A session has one registration: registering again replaces its token, which is
how a client reports a token its push service renewed. Clients register after
every sign-in and whenever the provider issues a new token. A token names one
device, so registering it takes it away from any other session that held it —
an earlier sign-in on the same device, or another account. An unknown
provider returns invalid_format, as does a token that is empty, longer than
4096 characters, or holds whitespace or anything outside printable ASCII.
Unregister
{"id":"push-2","type":"push.unregister"}
{"id":"push-2","type":"push.unregister","ok":true}
A registration also ends with its session — auth.logout, session.revoke,
expiry or replacement — and when the provider reports its token no longer
valid. Pushes then stop, and the session's entry in session.list shows
"push": false.
When a Device Is Woken
A session with no active connection is sent a push when:
message— a message from somebody else arrives in a conversation whose notifications the user has on: not muted withnotification.set, and formentions, mentioning the user. Sent with high priority, so that a sleeping device wakes. A direct transfer's offer wakes only a device whose client declaredtransfer.createdwhen it connected last: no other could answer it.read— the user reads a conversation on another device, so that a notification this device shows for it can go away. Sent with normal priority.joined— somebody from one of the user's address books signs up and is added to the contact list. Sent with normal priority.reaction— somebody's reaction to a message of the user's starts waiting for them, in a conversation whose notifications are notnone. Sent with high priority. Of the reactions of a conversation waiting to be pushed together, the push tells of the newest that still waits.reactions— reactions that waited for the user stop waiting: seen on another device, taken back or gone, so that a notification this device shows for them can go away. Sent with normal priority.
Other changes wait for the device's next sync. A session with an active connection gets no push: its device is in use and has the event already. A passive connection does not count, so a phone in the middle of a background sync still gets woken for what arrives after it. Pushes for one conversation merge: a user is pushed about a conversation at most once every few seconds, and what arrives meanwhile goes out as one push when that time is up, carrying the latest of what would itself have been pushed. A message the user has read or hid meanwhile on another device, or that was deleted, is no longer pushed, nor is a reaction that no longer waits.
Payload
Pushes are FCM data messages. Their values are strings:
{
"v": "2",
"reason": "message",
"user_id": "12345",
"session_id": "s_0123...",
"seq": "8",
"conversation_id": "direct:12345:67890",
"msg_id": "1002",
"sender": "67890",
"title": "Bob",
"messages": "[{\"msg_id\":\"1002\",\"conversation_id\":\"direct:12345:67890\",\"from\":67890,\"kind\":\"text\",\"payload\":{\"text\":\"Are we still on for tonight?\"},\"time\":\"2024-01-15T14:00:00Z\",\"revision\":1,\"reactions\":[]}]"
}
| Key | Description |
|---|---|
v |
Payload version, 2. A device that knows an older version only takes a push of a newer one for a signal to catch up |
reason |
message, read, joined, reaction or reactions |
user_id |
The account the push is for |
session_id |
The session it was sent to; a device that no longer holds that session drops it |
seq |
message: the message's place in the stream of conversation_id, a device whose cursor for that conversation is already there having nothing to do; reaction: the place of the reactions.updated that made it wait in the user's account stream, a device whose account cursor is already there having nothing to do |
conversation_id |
message, read, reaction and reactions: where it happened |
msg_id |
message: the message; reaction: the user's message reacted to |
sender |
message: the sender; reaction: who reacted |
reaction |
reaction only: the reaction |
reacted_at |
reaction only: when it was put, RFC 3339 |
contact |
joined: the person who signed up |
title |
message and reaction: the conversation's title as the user sees it — for a direct chat, the sender's name in the contact list, else their nick, else their ID; joined: the person's name in the contact list |
author |
message and reaction in a group or channel: the sender's name as the user sees it |
messages |
message only: the messages from others the user has not read, up to and including msg_id, newest first, as a JSON array of Message objects exactly as message.new carries them to the user — where the conversation tells only of mentions, those that mention the user or reply to them. As many go as fit, of the ten newest: FCM takes 4096 bytes of data, measured here as the JSON the data is sent as. A message to more than 64 members goes alone. Reactions are left out, [], and a bot's buttons too, for the synchronization to bring; the texts a message quotes or forwards go cut to their first 160 characters. The newest message, when it does not fit whole, goes with as much of its text as fits, cut after a word, its mentions past the cut left out, and "cut": true; older ones that do not fit stay behind. Absent when not even that fits |
authors |
message in a group or channel: the names of the senders among messages other than sender, as the user sees them, as a JSON object by user ID; a message goes along only with its sender's name. Absent when there are none |
preview |
reaction only: the first 100 characters of the text of the user's message, Voice message for a voice message, or the file name of another file message |
through |
read only: the new read cursor |
FCM reserves from, message_type and every key starting with google or
gcm, which is why the sender goes by sender here and not by from as
elsewhere in the protocol.
A device shows the messages a push carries as they are, at once: in the
notification and in the conversation, before it has synchronized or when it
cannot. A message that came cut shows that it is, and is not copied, forwarded
or edited until the device fetches it whole. The synchronization that follows
brings the same messages again, and any the push left behind. title, author
and authors name the notification and its writers. What a push carries travels through Google's
servers.
Pushes may arrive late, twice or out of order, and handling one again is
harmless: a device whose cursor for the conversation has reached a message
push's seq, whose account cursor has reached a reaction push's seq, or
whose read cursor has reached a read push's through, has nothing to do.