Skip to main content

Channel Storage

Channel storage provides persistent, per-key state on any channel. Storage entries are key-value pairs that live alongside the channel, surviving disconnects and reconnects. This gives clients a way to read, write, and observe shared state without round-tripping through your backend.

When to use storage

Storage is a good fit for state that should be immediately available to anyone who joins a channel:

  • Room or session settings — max players, game mode, room name
  • Shared cursors or selections — which cell a user is editing in a spreadsheet
  • Feature flags or configuration — per-channel toggles that take effect in real-time
  • User status — online/away/busy indicators tied to a channel
  • Last known state — the most recent value of something that changes over time (current score, latest price, active speaker)

Storage is not a general-purpose database. Each entry is a single key with a JSON value, scoped to a channel. For ordered, append-only data (like chat history), use message storage instead.

How it works

Each channel can have any number of storage entries. Each entry has a key (a string up to 128 characters) and a data value (any valid JSON, up to 32 KiB). Entries also track meta — the uid and umd of the last writer — so observers always know who set a value.

Permissions

Storage access is controlled per-key in JWT claims using the storage directive inside channel entries. Keys support wildcards (*) and regex patterns (#regex:) for flexible matching.

{
"exp": 1743580800,
"scope": "connect",
"channels": {
"game.lobby": {
"subscribe": true,
"storage": {
"settings": {
"observe": true,
"get": true
},
"player.*": {
"observe": true,
"get": true,
"set": true,
"store": 86400
}
}
}
}
}

Six directives control what a connection can do with matching keys:

DirectivePurpose
observeReceive real-time updates when the value changes, and get current values on subscribe
getRead the current value on demand
setWrite values
storeTTL in seconds for entries written by this connection (defaults to forever)
scheduleBeforeThe furthest future time this connection can schedule storage writes for
emitPubSubEventTrigger a backend pub/sub event when a value changes
info

Storage operations do not require an active channel subscription. Connect token permissions alone are sufficient for get and set.

Writing storage entries

Clients write storage entries by sending a hotsock.channelStorageSet message on the WebSocket:

> {"event":"hotsock.channelStorageSet", "channel":"game.lobby", "key":"settings", "data":{"maxPlayers":4}}

Writes that don't change the value are detected automatically and skip subscriber fan-out, so clients don't need to deduplicate on their end. Setting data to null or {} clears the entry.

Also available via the Client HTTP API at connection/channelStorageSet.

Server-side writes

Storage entries can be set from your backend using the Lambda or HTTP publish APIs by specifying "event": "hotsock.channelStorageSet" with a key field. Server-side writes bypass client permission checks, making them useful for initializing channel state before any clients connect.

{
"channel": "game.lobby",
"event": "hotsock.channelStorageSet",
"key": "settings",
"data": { "maxPlayers": 4 }
}

Scheduled writes

Storage writes can be scheduled for future delivery using scheduleExpression, just like scheduled messages. This works on the WebSocket, the Client HTTP API, and the server-side Lambda and HTTP publish APIs. Client-initiated scheduled storage writes require the scheduleBefore permission on the matching storage key.

> {"event":"hotsock.channelStorageSet", "channel":"game.lobby", "key":"round-state", "data":"finished", "scheduleExpression":"at(2026-04-08T15:00:00)"}

Reading storage entries

Clients read a single entry by sending a hotsock.channelStorageGet message:

> {"event":"hotsock.channelStorageGet", "channel":"game.lobby", "key":"settings"}

The response is a hotsock.channelStorageData message:

{
"event": "hotsock.channelStorageData",
"channel": "game.lobby",
"key": "settings",
"data": { "maxPlayers": 4 },
"meta": { "uid": "host", "umd": null }
}

If no value has been set for the key, data and meta are null.

Also available via the Client HTTP API at connection/channelStorageGet. To list multiple entries at once, use connection/channelStorageList.

Observing storage entries

When a key has observe enabled, two things happen:

  1. On subscribe — all storage entries matching the connection's observe patterns are delivered as hotsock.channelStorageUpdated messages immediately after the hotsock.subscribed message. New subscribers are caught up the moment they join.

  2. On change — whenever an observed key's value changes, all observers receive a hotsock.channelStorageUpdated message in real-time:

{
"event": "hotsock.channelStorageUpdated",
"channel": "game.lobby",
"key": "settings",
"data": { "maxPlayers": 6 },
"meta": { "uid": "host", "umd": null }
}
info

Unlike message echo behavior, hotsock.channelStorageUpdated is always delivered back to the connection that set the value (when that connection observes the key). Storage updates are state notifications, not message echoes.

TTL and expiration

By default, storage entries are retained forever. The store directive sets a TTL in seconds for entries written by a connection. Server-side writes can also specify a store value to control TTL.

When an entry expires, it is removed from storage. WebSocket observers are not notified of TTL expirations — expired entries simply stop appearing in observe deliveries on subscribe and in get responses. This is because DynamoDB TTL cleanup is a background process that can lag up to 48 hours behind the actual expiration time, making it unsuitable as a notification trigger for a real-time service.

If you need observers to be notified when a value expires, explicitly set the storage entry to null at the desired time — either by publishing a scheduled storage write or by invoking a hotsock.channelStorageSet from your backend when the expiration should take effect.

Backend events

When the emitPubSubEvent directive is enabled, storage value changes emit a hotsock.channelStorageUpdated event to SNS/EventBridge. This includes creates, updates, deletes, and TTL expirations. The event includes the current value, the previous value, and expiration metadata.

Server-side access

The admin API provides read access to channel storage: