Skip to main content

Message reactions, presence pub/sub, and heartbeat enforcement

ยท 4 min read

Hotsock v1.13 introduces message reactions, presence member events on pub/sub (SNS/EventBridge), and a heartbeatTimeout claim for server-enforced connection liveness.

Message reactionsโ€‹

Clients can now add and remove reactions on stored messages, with real-time delivery to all channel subscribers, aggregated counts in message history, and a separate endpoint to list who reacted with what.

Reactions are gated by a new react directive on channel message event patterns, alongside the existing publish, store, echo, and other directives. A connection needs three things to react: an active channel subscription, a uid, and a stored target message that hasn't expired.

Setting up permissionsโ€‹

The react directive accepts true (allow any reaction), false (deny), or an array of strings to restrict which reactions are permitted:

{
"exp": 1747574400,
"scope": "connect",
"uid": "Jim",
"channels": {
"room.123": {
"subscribe": true,
"messages": {
"chat": {
"publish": true,
"echo": true,
"store": 31536000,
"react": ["๐Ÿ‘", "โค๏ธ", "๐Ÿ˜‚"]
}
}
}
}
}

Adding and removing reactionsโ€‹

Clients send a hotsock.messageReaction message on the WebSocket with the target message ID, the reaction value, and an explicit add or remove action:

> {"event":"hotsock.messageReaction", "channel":"room.123", "data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW", "reaction":"๐Ÿ‘", "action":"add"}}

All channel subscribers immediately receive a hotsock.messageReactionAdded event:

< {"id":"01JB4K7M2N...","event":"hotsock.messageReactionAdded","channel":"room.123","data":{"messageId":"01JA3S0TNEC2QBYMZRBMCEXGNW","reaction":"๐Ÿ‘","action":"add"},"meta":{"uid":"Jim","umd":null}}

Reaction data can be emoji characters, Slack-style colon-wrapped strings like :thumbsup:, or any short string up to 100 bytes. Each user can have one reaction per reaction value per message.

Reactions in message historyโ€‹

listMessages responses now include a reactions object on each message that has reactions, keyed by reaction value:

{
"messages": [
{
"id": "01JA3S0P7FB7WVYS67X316M32S",
"event": "chat",
"channel": "room.123",
"data": "Wow. Can we make it a different moment?",
"meta": { "uid": "Jim", "umd": null },
"reactions": { "๐Ÿ‘": { "count": 2 }, "โค๏ธ": { "count": 1 } }
}
]
}

Pass expandReactions: true to also include an items array per reaction with the user behind each one:

"reactions": {
"๐Ÿ‘": {
"count": 2,
"items": [
{ "uid": "Jim", "umd": { "name": "Jim Halpert" } },
{ "uid": "Pam" }
]
}
}

You can also fetch reactions for a single message with the dedicated connection/listReactions endpoint.

Pub/sub events for reactionsโ€‹

hotsock.messageReactionAdded and hotsock.messageReactionRemoved events are emitted to SNS/EventBridge after reactions are persisted, gated by the emitPubSubEvent directive on the matching message event pattern.

Presence member events on pub/sub (SNS/EventBridge)โ€‹

Presence channel member lifecycle events (hotsock.memberAdded, hotsock.memberRemoved, hotsock.memberUpdated) are now also emitted to SNS/EventBridge, gated by the existing PublishEventsToSNSParameter / PublishEventsToEventBridgeParameter settings. The pub/sub events are deduplicated by uid to match WebSocket fan-out semantics, so joining from a second device when you're already present doesn't trigger another memberAdded.

The pub/sub data payload uses the same single-entity shape as the rest of the public event types, with uid, umd, and members at the root:

{
"uid": "Dwight",
"umd": { "status": "available" },
"members": [
{ "uid": "Jim", "umd": null },
{ "uid": "Dwight", "umd": { "status": "available" } }
]
}

dataType is channelMember, so SNS filter policies and EventBridge rules can target presence events specifically.

Server-enforced heartbeatsโ€‹

The new heartbeatTimeout connect-token claim sets a maximum interval between client-initiated hotsock.heartbeat messages. If the server doesn't see a heartbeat within that window, the connection is forcefully disconnected:

{
"scope": "connect",
"heartbeatTimeout": 30
}

Valid values are 5 to 600 seconds. This is most useful on presence channels where accurate detection of dropped clients matters more than waiting for API Gateway's 10-minute idle timeout. The client just needs to send {"event":"hotsock.heartbeat"} periodically, and the server records receipt and reschedules the next check relative to the last heartbeat, with no reply.

Wrapping upโ€‹

Existing installations with auto-update enabled are already running v1.13 and have access to these features today. Other installations can be manually updated at any time. A full changelog is available with the complete list of changes included in this release.