Handshake
After establishing a WebSocket connection, the client must send
connection.hello to declare supported message types (capabilities). This must
be the first message before any other communication.
Hello Request
Client:
{
"type": "connection.hello",
"data": {
"types": [
"auth.code", "auth.phone", "auth.qr", "auth.token", "auth.refresh", "auth.logout", "qr.get", "qr.accept",
"session.list", "session.revoke", "phone.get", "phone.code", "phone.set", "push.register", "push.unregister", "activity.set",
"transport.list", "transport.setup", "transport.password", "transport.enable", "transport.disable", "transport.reset",
"message.send", "message.forward", "message.edit", "message.delete", "message.react", "message.play",
"message.list", "message.search", "message.clear", "read.set", "delivery.set",
"pin.set", "pin.remove", "pin.list", "notification.set", "draft.update", "reactions.see",
"block.add", "block.remove", "block.list", "privacy.get", "privacy.update",
"ban.add", "ban.remove", "ban.list", "restriction.set", "sync.state", "sync.pull",
"conversation.create", "conversation.list", "conversation.search", "conversation.get", "conversation.update", "conversation.delete",
"member.list", "member.add", "member.update", "member.remove",
"invite.create", "invite.list", "invite.get", "invite.accept", "invite.revoke", "invite.update",
"contact.list", "contact.add", "contact.rename", "contact.delete",
"phonebook.update", "phonebook.get", "phonebook.clear",
"presence.get", "presence.set", "profile.get", "profile.update", "user.search", "typing.set",
"file.create", "file.get", "file.complete", "file.abort",
"transfer.create", "transfer.accept", "transfer.decline", "transfer.cancel", "button.press", "button.answer",
"pack.list", "pack.get", "pack.install", "pack.remove", "pack.order",
"session.revoked", "transport.updated", "phone.updated", "qr.accepted",
"message.created", "message.edited", "message.deleted", "message.reacted",
"message.cleared", "message.played", "read.updated", "delivery.updated", "pin.added", "pin.removed", "notification.updated", "draft.updated",
"reactions.updated",
"block.added", "block.removed", "privacy.updated",
"ban.added", "ban.removed", "restriction.updated",
"conversation.created", "conversation.updated", "conversation.deleted",
"member.added", "member.updated", "member.removed",
"invite.created", "invite.accepted", "invite.revoked", "invite.updated",
"contact.added", "contact.renamed", "contact.deleted", "contact.requested",
"presence.online", "presence.offline", "presence.updated", "presence.self", "profile.updated", "file.updated", "typing.changed",
"transfer.created", "transfer.accepted", "transfer.declined", "transfer.cancelled", "button.pressed",
"pack.installed", "pack.removed", "pack.ordered"
]
}
}
| Field | Type | Description |
|---|---|---|
types |
array of strings | Message types the client supports |
active |
boolean | Optional, default true. false opens the connection passive, as a background sync does |
Hello Response
Success:
{
"type": "connection.hello",
"ok": true,
"data": {
"types": [
"auth.code", "auth.phone", "auth.qr", "auth.token", "auth.refresh", "auth.logout", "qr.get", "qr.accept",
"session.list", "session.revoke", "phone.get", "phone.code", "phone.set", "push.register", "push.unregister", "activity.set",
"transport.list", "transport.setup", "transport.password", "transport.enable", "transport.disable", "transport.reset",
"message.send", "message.forward", "message.edit", "message.delete", "message.react", "message.play",
"message.list", "message.search", "message.clear", "read.set", "delivery.set",
"pin.set", "pin.remove", "pin.list", "notification.set", "draft.update", "reactions.see",
"block.add", "block.remove", "block.list", "privacy.get", "privacy.update",
"ban.add", "ban.remove", "ban.list", "restriction.set", "sync.state", "sync.pull",
"conversation.create", "conversation.list", "conversation.search", "conversation.get", "conversation.update", "conversation.delete",
"member.list", "member.add", "member.update", "member.remove",
"invite.create", "invite.list", "invite.get", "invite.accept", "invite.revoke", "invite.update",
"contact.list", "contact.add", "contact.rename", "contact.delete",
"phonebook.update", "phonebook.get", "phonebook.clear",
"presence.get", "presence.set", "profile.get", "profile.update", "user.search", "typing.set",
"file.create", "file.get", "file.complete", "file.abort",
"transfer.create", "transfer.accept", "transfer.decline", "transfer.cancel", "button.press", "button.answer",
"pack.list", "pack.get", "pack.install", "pack.remove", "pack.order",
"session.revoked", "transport.updated", "phone.updated", "qr.accepted",
"message.created", "message.edited", "message.deleted", "message.reacted",
"message.cleared", "message.played", "read.updated", "delivery.updated", "pin.added", "pin.removed", "notification.updated", "draft.updated",
"reactions.updated",
"block.added", "block.removed", "privacy.updated",
"ban.added", "ban.removed", "restriction.updated",
"conversation.created", "conversation.updated", "conversation.deleted",
"member.added", "member.updated", "member.removed",
"invite.created", "invite.accepted", "invite.revoked", "invite.updated",
"contact.added", "contact.renamed", "contact.deleted", "contact.requested",
"presence.online", "presence.offline", "presence.updated", "presence.self", "profile.updated", "file.updated", "typing.changed",
"transfer.created", "transfer.accepted", "transfer.declined", "transfer.cancelled", "button.pressed",
"pack.installed", "pack.removed", "pack.ordered"
]
}
}
Server responds with the list of types it supports. The intersection of client and server types determines what can be used in the session. A signed-in connection's session remembers the intersection its client said last, for when nothing of it is connected: a device a push wakes is woken only for what its client can answer, such as a direct transfer's offer.
A server whose service has links of its own also answers link_base, such as
"link_base":"https://mandarin.im": where every link it hands out starts —
invitations, bots and QR sign-in
codes. A
client opens such links itself instead of leaving them to a browser, and an app
of the service claims them on its platform (Android App Links, iOS Universal
Links). Without link_base there are no such links.
Failure:
{
"type": "connection.hello",
"ok": false,
"error": {
"code": "no_common_types",
"message": "No common message types supported"
}
}
Capability Negotiation
- Server only sends real-time events — signals that belong to no stream — of types the client declared
- Every event of the client's streams reaches it whatever it declared: a stream is followed whole, and an event of a type the client does not know is a place it passes over, never a gap (see Following Streams)
- Client should only send application requests of types the server declared
connection.*control types are mandatory and are not negotiated- Unknown types received should be ignored (forward compatibility)
- A
typeslist sharing nothing with the server — including an empty one — is refused withno_common_types: no application operation could be exchanged
Protocol Evolution
When a message type needs breaking changes, a new type is introduced:
// Existing client
{"types": ["message.send", ...]}
// New client with updated message format
{"types": ["message.send", "message.send.v2", ...]}
Server uses the most advanced type both parties support. Old clients continue to work with original types.
An event of a stream is kept once, in no particular form, and given its form as it is delivered — live or in a pull: the newest version of its type the client declared, the original one when it declared none. A stream therefore holds one event for every version, and a client that knows only the original form reads every place of it.