Skip to content

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.

  • presence.joined, presence.left, presence.changed
  • events/auction.mdauction.subscribe

Notes

  • Implementation status: Implemented.
  • Use it to initialize presence state; then apply presence.joined / presence.left deltas 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).

  • 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.

  • presence.joined, presence.changed

Notes

  • Implementation status: Implemented (AuctionPresenceService.leave).
  • The server sweeps stale connections periodically, so a presence.left can 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.

  • 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 connectionCount in place; do not treat presence.changed as 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.

  • presence.changed (canonical), presence.joined, presence.left

Notes

  • Implementation status: Planned. Today, presence refresh is automatic.
  • Contract alias: the documented presence.update maps to the canonical server event presence.changed when counting/state changes.

Best Practices

  • Do not send presence.update until the release notes announce it.
  • Rely on presence.snapshot + presence.joined/left/changed for UI state.

  • events/auction.md — subscribe flow that produces presence.snapshot and drives join/leave.
  • rooms.mdauction:<cycleId> room delivery.
  • errors.md — error codes referenced above.