Presence Events¶
Presence events track which subscribers are currently connected to an auction room. Presence is server-managed: the server records a subscriber's connections when it subscribes and removes them when it unsubscribes or disconnects. Clients observe presence through server-pushed events and the initial presence.snapshot.
Reference: events/auction.md · rooms.md
Event list¶
| Event | Direction | Status |
|---|---|---|
presence.snapshot | Server → Client | Implemented (after auction.subscribe) |
presence.joined | Server → Client | Implemented |
presence.left | Server → Client | Implemented |
presence.changed | Server → Client | Implemented |
presence.update | Client → Server | Planned (alias of presence.changed) |
Contract ↔ implementation mapping: the documented client event presence.update corresponds to the canonical server event presence.changed. Presence changes are not client-initiated in the current build; the server publishes them.
Presence participant shape¶
All presence events carry a participant object:
{
"participant": {
"subscriberId": "sub_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"enrolledSubscriberId": "enr_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"name": "Annie Mohan",
"avatar": "https://cdn.example.com/avatars/av_01HQ5B.png",
"lastSeenAt": "2026-08-03T10:00:00.000Z",
"connectionCount": 1
}
}
| Field | Type | Description |
|---|---|---|
subscriberId | string | The subscriber's user id. |
enrolledSubscriberId | string | The enrollment id identifying the participant in the auction. |
name | string | null | Display name. |
avatar | string | null | Avatar URL. |
lastSeenAt | string | ISO-8601 last activity timestamp. |
connectionCount | number | Number of open connections for this subscriber in the auction. |
presence.snapshot¶
Description¶
Sent once after auction.subscribe. Lists the subscribers currently online in the auction, so a freshly connected client can render presence immediately without replaying join events.
Direction¶
Server → Client
Roles Allowed¶
All roles that can subscribe (auction:subscribe).
Permissions Required¶
auction:subscribe (inherited from the subscribing connection).
Request Schema¶
N/A — server-pushed.
Response Schema¶
| Field | Type | Description |
|---|---|---|
data.auctionId | string | Auction id. |
data.cycleId | string | Cycle id. |
data.payload.onlineSubscribers | array | Array of participant objects. |
Success Response¶
{
"type": "presence.snapshot",
"requestId": "req_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"data": {
"auctionId": "auc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"cycleId": "cyc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"occurredAt": "2026-08-03T10:00:00.000Z",
"payload": {
"onlineSubscribers": [
{
"subscriberId": "sub_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"enrolledSubscriberId": "enr_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"name": "Annie Mohan",
"avatar": null,
"lastSeenAt": "2026-08-03T10:00:00.000Z",
"connectionCount": 1
}
]
}
}
}
Error Responses¶
None (server-pushed).
Validation Rules¶
N/A.
Broadcast Behaviour¶
Unicast to the subscribing connection only.
Related Events¶
presence.joined,presence.left,presence.changed- events/auction.md →
auction.subscribe
Notes¶
- Implementation status: Implemented.
- Use it to initialize presence state; then apply
presence.joined/presence.leftdeltas on top.
Best Practices¶
- Treat the snapshot as the base and deltas as mutations; do not clear your presence map on every event.
presence.joined¶
Description¶
Published when a subscriber becomes online in an auction (first connection for that subscriber). Subsequent connections of the same subscriber emit presence.changed instead, incrementing connectionCount.
Direction¶
Server → Client
Roles Allowed¶
All subscribed roles.
Permissions Required¶
None at the event level (delivered to subscribers of the auction).
Request Schema¶
N/A — server-pushed.
Response Schema¶
| Field | Type | Description |
|---|---|---|
data.auctionId | string | Auction id. |
data.cycleId | string | Cycle id. |
data.occurredAt | string | ISO-8601 timestamp. |
data.payload.participant | object | Presence participant. |
Example Response¶
{
"type": "presence.joined",
"data": {
"auctionId": "auc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"cycleId": "cyc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"occurredAt": "2026-08-03T10:00:05.000Z",
"payload": {
"participant": {
"subscriberId": "sub_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"enrolledSubscriberId": "enr_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"name": "Annie Mohan",
"avatar": null,
"lastSeenAt": "2026-08-03T10:00:05.000Z",
"connectionCount": 1
}
}
}
}
Error Responses¶
None.
Validation Rules¶
N/A.
Broadcast Behaviour¶
Broadcast to the auction:<cycleId> room (all subscribers, across all servers).
Related Events¶
presence.left,presence.changed,presence.snapshot
Notes¶
- Implementation status: Implemented (
AuctionPresenceService.join). - Subscribers' own socket receives the event too; filter self if the UI should not show the current user in a list.
Best Practices¶
- Add the participant to the local presence map.
- The event carries no
requestId; never correlate it to a client message.
presence.left¶
Description¶
Published when a subscriber's last connection to an auction leaves (disconnect, unsubscribe, or timeout). connectionCount is decremented by the server; when it reaches zero the subscriber is offline and this event is published.
Direction¶
Server → Client
Roles Allowed¶
All subscribed roles.
Permissions Required¶
None.
Request Schema¶
N/A — server-pushed.
Response Schema¶
| Field | Type | Description |
|---|---|---|
data.payload.participant | object | Presence participant (with reduced connectionCount). |
Example Response¶
{
"type": "presence.left",
"data": {
"auctionId": "auc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"cycleId": "cyc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"occurredAt": "2026-08-03T10:10:00.000Z",
"payload": {
"participant": {
"subscriberId": "sub_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"enrolledSubscriberId": "enr_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"name": "Annie Mohan",
"avatar": null,
"lastSeenAt": "2026-08-03T10:10:00.000Z",
"connectionCount": 0
}
}
}
}
Error Responses¶
None.
Validation Rules¶
N/A.
Broadcast Behaviour¶
Broadcast to the auction:<cycleId> room.
Related Events¶
presence.joined,presence.changed
Notes¶
- Implementation status: Implemented (
AuctionPresenceService.leave). - The server sweeps stale connections periodically, so a
presence.leftcan arrive without a clean disconnect.
Best Practices¶
- Remove the participant (or mark offline) from the local presence map.
presence.changed¶
Description¶
Published when an already-online subscriber opens or closes an additional connection, changing connectionCount without flipping online/offline state. This is the canonical server event for the documented alias presence.update.
Direction¶
Server → Client
Roles Allowed¶
All subscribed roles.
Permissions Required¶
None.
Request Schema¶
N/A — server-pushed.
Response Schema¶
| Field | Type | Description |
|---|---|---|
data.payload.participant | object | Presence participant (with updated connectionCount). |
Example Response¶
{
"type": "presence.changed",
"data": {
"auctionId": "auc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"cycleId": "cyc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"occurredAt": "2026-08-03T10:00:06.000Z",
"payload": {
"participant": {
"subscriberId": "sub_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"enrolledSubscriberId": "enr_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"name": "Annie Mohan",
"avatar": null,
"lastSeenAt": "2026-08-03T10:00:06.000Z",
"connectionCount": 2
}
}
}
}
Error Responses¶
None.
Validation Rules¶
N/A.
Broadcast Behaviour¶
Broadcast to the auction:<cycleId> room.
Related Events¶
presence.joined,presence.left
Notes¶
- Implementation status: Implemented.
- Multi-device subscribers show
connectionCount > 1; the subscriber remains online until the count returns to zero.
Best Practices¶
- Update
connectionCountin place; do not treatpresence.changedas a join/leave.
presence.update¶
Description¶
Planned. A client→server event used to refresh the subscriber's presence activity (e.g. a manual "I'm still here" signal). In the current build presence is refreshed by the server automatically through the transport heartbeat, so clients should not send presence.update yet. When implemented, it updates the participant's lastSeenAt and returns the refreshed participant.
Direction¶
Client → Server
Roles Allowed¶
SUBSCRIBER (subscribers only; presence is a subscriber concept).
Permissions Required¶
auction:subscribe.
Request Schema¶
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | presence.update |
data.cycleId | string | Yes | Cycle id of the auction. |
Response Schema¶
| Field | Type | Description |
|---|---|---|
data.participant | object | Refreshed participant with updated lastSeenAt. |
Success Response¶
{
"type": "ack",
"requestId": "req_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"eventType": "presence.update",
"data": {
"participant": {
"subscriberId": "sub_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"enrolledSubscriberId": "enr_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"name": "Annie Mohan",
"avatar": null,
"lastSeenAt": "2026-08-03T10:00:00.000Z",
"connectionCount": 1
}
}
}
Error Responses¶
| Code | Description |
|---|---|
FORBIDDEN | Caller is not a subscriber or not subscribed. |
NOT_FOUND | Auction/cycle not found or no participation. |
BAD_REQUEST | cycleId missing/invalid. |
Validation Rules¶
| Rule | Behaviour |
|---|---|
cycleId non-empty string | Attempt refresh. |
cycleId missing | BAD_REQUEST. |
| Not subscribed to the cycle | NOT_FOUND / FORBIDDEN. |
Example Request¶
{
"type": "presence.update",
"requestId": "req_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"data": { "cycleId": "cyc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X" }
}
Broadcast Behaviour¶
None; it refreshes the sender's presence only. Other clients receive presence.changed when connection counts change, not on plain refresh.
Related Events¶
presence.changed(canonical),presence.joined,presence.left
Notes¶
- Implementation status: Planned. Today, presence refresh is automatic.
- Contract alias: the documented
presence.updatemaps to the canonical server eventpresence.changedwhen counting/state changes.
Best Practices¶
- Do not send
presence.updateuntil the release notes announce it. - Rely on
presence.snapshot+presence.joined/left/changedfor UI state.
Related documents¶
- events/auction.md — subscribe flow that produces
presence.snapshotand drives join/leave. - rooms.md —
auction:<cycleId>room delivery. - errors.md — error codes referenced above.