Ordering and limits
Events are not ordered, and the same change can reach a client more than once: twice on the socket, or once on the socket and once in a catch-up. Three rules make every copy safe to apply: deduplicate on id, keep the higher seq, and move pointers forward only.
How it works
Section titled “How it works”Deduplicate on id
Section titled “Deduplicate on id”Every event arrives in the envelope {type, id, ts, data}. id is a UUIDv7. Drop an event whose id you have already handled. With presence.fanout on, presence.changed can arrive twice, on the peer’s presence channel and on your own channel, with the same id.
The frame remembers the last 1000 event ids.
Keep the copy with the higher seq
Section titled “Keep the copy with the higher seq”message.created, message.updated and conversation.updated carry the seq of their Message or ConversationSummary, and catch-up rows carry it too. When two copies of the same message or inbox row meet, keep the one with the higher seq. Never replace a copy with one of a lower seq, whether it comes from an event or a catch-up.
- A message’s
seqis its place in its conversation’s sync order. - An inbox row’s
seqis the viewer’s inbox seq for that row.
Pointers only move forward
Section titled “Pointers only move forward”read.updated and delivered.updated carry a message id: last_read_message_id or last_delivered_message_id. Read it as “everything up to this message”. Message ids are UUIDv7 and sort by creation as strings, so mark each of your own messages with an id less than or equal to the pointer, and never unmark one. A late event with an older pointer then changes nothing.
The participants pointers in a thread catch-up work the same way.
seq has gaps
Section titled “seq has gaps”seq counts every change, including ones you never see: a message the other side deleted for themselves, a send that was dropped silently. Never infer missing data from a gap. Ask the catch-up routes for what changed instead.
Events without seq
Section titled “Events without seq”The other events carry no seq: read.updated, delivered.updated, typing, presence.changed, photos.updated, call.ringing, call.updated, call.capture, purchase.updated and session.revoked. Deduplicate them on id. For calls and purchases, the recovery calls give the current state after a gap.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Subscriptions per socket | 200 |
| Presence subscriptions the frame keeps | 100 |
| Socket lifetime | 24 hours |
| Keep-alive from AppSync | every 60 s |
| Session token lifetime | 900 s |
| Presence pass lifetime | 1 hour |
| Events per publish | 5, each under 240 KB |
GET /v1/conversations?changed_since page |
limit at most 50 |
GET /v1/conversations/{id}/changes page |
limit 1 to 200, default 100 |
| Typing | send at most every 3 s, show for 5 s |
Where it ends
Section titled “Where it ends”seqonly compares copies of the same message or the same inbox row. Events of different types have no order between them.seqdoes not tell you what is missing, only which copy is newer.- Presence, typing and settings changes take no
seq. Recover presence by reloading it, as presence subscriptions explains.