Docs menu

Hub events

The realtime hub is a SignalR hub at /hub/rooms. @dotwire/client wraps it, so you only need this page if you're writing your own client or debugging the wire protocol.

Connecting

  • Transport: WebSockets only. Skip negotiation; there is no long-polling or SSE fallback.
  • Protocol: the SignalR JSON hub protocol. MessagePack isn't available.
  • Auth: pass the token as the access_token query parameter. Browsers can't set headers on a WebSocket.
  • Node role: served by All and Gateway nodes.
  • Keep-alive: the server pings every 30 seconds.
  • Token expiry: the server closes the connection when the token expires. Reconnect with a fresh token.

SignalR stateful reconnect is turned off. After a reconnect, call Subscribe again for each room and fetch missed messages with GET /rooms/{roomId}/messages?afterSeq=<last seq>. See Ordering and gap-fill.

Client-to-server methods

MethodArgumentsBehavior
SubscriberoomId (UUID)Checks the role cross-check and room membership, then joins the room. Queues a presence join. Fails with Forbidden if either check fails.
UnsubscriberoomIdLeaves the room and queues a presence leave.
TypingroomId, isTyping (boolean)Broadcasts a typing notification to the room. Ignored unless the connection is subscribed to the room.

Membership is checked when you call Subscribe, not on each event. A revocation removes the subscription immediately.

Subscribe and Unsubscribe share the per-connection hub rate bucket. Over the limit, they fail with RateLimited.

Typing has its own cooldown: the server drops a true notification that arrives within 0.5 seconds of the last one for that room, and drops a false notification unless the connection was typing.

There is no hub method for sending messages. Send with POST /rooms/{roomId}/messages; see the HTTP API.

When the connection closes, the server queues a presence leave for every room it was subscribed to.

Server-to-client events

EventPayloadWhen
ReceiveMessage{ roomId, seq, senderId, time, content }A message was accepted in a subscribed room. Content is decrypted.
UserTyping{ roomId, userId, isTyping }A member started or stopped typing.
PresenceUpdated{ roomId, joined, left }Coalesced presence changes. joined and left are arrays of user ids.
MessageRetracted{ roomId, seq }A message was redacted. Remove it from your UI.
MembershipRevoked{ roomId, userId }This user was removed from the room, and the connection was unsubscribed from it.

Each event has one argument, a JSON object with camelCase fields. seq is a number. time is an ISO 8601 timestamp.

ReceiveMessage is the only event that is also stored. Typing, presence, retraction, and revocation events are ephemeral: a client that misses them doesn't get them back.

Errors

Hub method failures arrive as SignalR errors with these messages:

MessageCause
UnauthorizedThe connection's token has no sub or dw:role.
ForbiddenThe role cross-check failed, or the user isn't a member of the room.
RateLimitedThe connection's hub bucket is empty.

The server doesn't send detailed errors to clients.

SSE equivalents

The SSE endpoint carries the same events under lowercase names: message, typing, presence, retracted, and revoked. See Stream room events.