Skip to content

Dichit WebSocket API

The Dichit real-time API delivers live auction data, presence, notifications, and staff controls over a single authenticated WebSocket connection. Subscribers, companies, auctioneers, and administrators all use the same endpoint; the server derives each connection's identity, role, and permissions from the JWT presented during the handshake.

This documentation is the authoritative contract for the real-time API. It is written for frontend engineers, backend engineers, mobile developers, QA engineers, and third-party integrators.


Overview

The real-time API is a hybrid async CQRS protocol over WebSocket (RFC 6455) with a server-pushed event stream. Clients send JSON messages inside a common envelope; the server validates and authorizes each message. Write commands return an immediate command.acknowledged with no business data, and business outcomes arrive asynchronously as domain events. Queries (e.g. auction.subscribe) return a synchronous ack. State changes (new bids, auction state transitions, presence transitions) are pushed to clients asynchronously.

⚠️ Breaking change: write commands migrated to async CQRS. Frontend teams must read MIGRATION-ASYNC-CQRS.md before updating.

Key properties:

  • One endpoint for every role.
  • JWT authentication at connection time.
  • Permission-based, message-level authorization.
  • Room-based fan-out backed by Redis Pub/Sub, so any WebSocket server instance can deliver to any client.
  • Server-driven event log with sequence numbers, enabling snapshot + replay recovery after reconnection.
  • Commands are correlated with their outcomes via correlationId / causationId.
  • Commands are idempotent (replays do not create duplicate bids or transitions).

Endpoint

All roles connect to a single WebSocket endpoint:

Production:   wss://api.example.com/ws
Staging:      wss://api.staging.example.com/ws
Local dev:    ws://localhost:3500/ws

There are no role-specific endpoints. Requesting a path other than /ws is not supported. See connection.md.

Supported protocol

  • Transport: WebSocket (RFC 6455) over TLS (wss://). Plain ws:// is allowed only for local development.
  • Subprotocols:
  • websocket.v1 — current protocol version (use this).
  • auction-room.v1 — legacy, accepted for backward compatibility.
  • bearer.<access-token> — subprotocol used to carry the JWT when a custom Authorization header cannot be set (for example, in browsers).
  • Framing: JSON text frames. Binary frames are not supported.
  • The Sec-WebSocket-Protocol header may carry websocket.v1, bearer.<token>.

See connection.md and versioning.md.

Authentication

Authentication happens during the WebSocket handshake, before any message is processed. An access token (JWT, type: access) is required for every connection. Clients present the token either as a standard HTTP header or as a WebSocket subprotocol:

Authorization: Bearer <access-token>

or

Sec-WebSocket-Protocol: websocket.v1, bearer.<access-token>

Invalid, expired, or missing tokens close the connection with close code 1008 and reason Authentication failed.

Roles are derived from the token claim:

Role Description Key permissions
SUBSCRIBER Individual auction participant auction:subscribe, auction:bid:place, room:join, room:leave
COMPANY Company user / auctioneer staff auction:subscribe, auction:staff, broadcast:receive, room:join, room:leave
SUPERADMIN Platform administrator auction:subscribe, auction:staff, broadcast:receive, room:join, room:leave

See authentication.md.

Message format

Every client-to-server message uses the same envelope:

{
  "id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
  "type": "auction.bid.place",
  "version": "1.0",
  "source": "web-client",
  "timestamp": "2026-08-05T10:00:00.000Z",
  "data": {
    "auctionId": "auc_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
    "amountMinor": "2500000"
  }
}
Field Type Required Description
type string Yes Dot-separated event name, e.g. auction.bid.place. 1–128 chars.
id string Yes (recommended) Client-generated identifier echoed back in acknowledgements and errors. 1–128 chars.
version string No Client protocol version. Informational only.
source string No Client identifier for debugging. Informational only.
timestamp string No Client-side ISO-8601 timestamp. Server does not trust it for ordering.
data object Depends Event payload, validated against the per-event Zod schema.
meta object No Reserved extension object, e.g. { "traceId": "..." }.

Server responses come in three shapes: acknowledgements, errors, and server-pushed events.

A synchronous query acknowledgement:

{
  "id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
  "type": "ack",
  "version": "1.0",
  "source": "dichit-backend",
  "timestamp": "2026-08-05T10:00:00.000Z",
  "sequence": 45218,
  "data": {
    "eventType": "auction.subscribe",
    "auctionId": "auc_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
    "cycleId": "cyc_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
    "room": "auction:auc_01HQ5BXWYP1Y5RX0W5X0W5X0W5X"
  }
}

A command acknowledgement carries no business data — outcomes arrive as pushed domain events:

{
  "id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
  "type": "command.acknowledged",
  "version": "1.0",
  "source": "dichit-backend",
  "timestamp": "2026-08-05T10:00:00.000Z",
  "sequence": 45218,
  "correlationId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
  "causationId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
  "data": {
    "command": "auction.bid.place",
    "status": "accepted"
  }
}
{
  "id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
  "type": "error",
  "version": "1.0",
  "source": "dichit-backend",
  "timestamp": "2026-08-05T10:00:00.000Z",
  "sequence": 45219,
  "data": {
    "code": "FORBIDDEN",
    "message": "You are not authorized to perform this WebSocket action."
  }
}

Server-pushed events (bids, presence, auction state) use the same envelope; the server adds a per-connection sequence and the type is the canonical event name such as auction.bid.created. For auction domain events, data carries { auctionId, cycleId, stateVersion, serverSequence?, payload }.

See protocol.md for the full contract and errors.md for all error codes.

Rooms

State is distributed through named rooms. Clients join rooms they are allowed to see, and the server publishes updates to a room exactly once, regardless of how many WebSocket servers are running:

Room Purpose
auction:<auctionId> Live auction state, bids, and presence
auction:staff:<auctionId> Staff-only auction events (auto-joined by auction.subscribe for COMPANY / SUPERADMIN)
company:<companyId> Company-scoped staff updates
user:<userId> Private updates for a single user
admins Platform-wide administrative broadcasts
notifications User notifications

Room membership is per-connection and cleared automatically when a connection closes. See rooms.md.

Breaking change (auctionId): WS auction rooms and request data now use auctionId (auction:<auctionId>, auction:staff:<auctionId>, channels auction:realtime:<auctionId>). The row is guaranteed before READY/LIVE (ensureAuctionsForEligibleCycles); fetch auctionId via REST (e.g. GET /v2/.../auctions/by-cycle/:cycleId) before subscribing. REST payloads still carry both auctionId and cycleId.

Documentation index

Getting started

Document Content
protocol.md Message envelope, request/response model, event naming, client/server events
connection.md Endpoint, TLS, reconnection, heartbeats, timeouts, message size
authentication.md JWT flow, token expiration, re-authentication, roles and permissions
rooms.md Room architecture, membership rules, lifetimes

Events

Document Content
events/system.md system.ping, system.pong
events/auth.md auth.login, auth.logout
events/auction.md auction.subscribe, auction.unsubscribe, auction.bid.place, auction.bid.cancel, auction.state.changed, auction.bid.created, auction.announcement.create, auction.announcement.created, auction.presence.floor.update, staff commands, server events
events/company.md Company-scoped events and the company:{companyId} room
events/presence.md auction.audience.snapshot, auction.audience.changed, auction.presence.floor.update, viewer/participant modes and floor presence
events/room.md room.join, room.leave, room-name access rules
events/notification.md notification.created, notification.read
events/chat.md chat.send, chat.typing

Reference

Document Content
errors.md Error codes, HTTP equivalents, retryability, examples
rate-limits.md Per-event limits and server behaviour when exceeded
lifecycle.md Full connection lifecycle: connect → auth → join → updates → heartbeat → disconnect → reconnect
versioning.md Protocol versioning, compatibility, deprecation policy
diagrams.md Mermaid diagrams for connection, auth, bidding, broadcast, Redis Pub/Sub, scaling, rooms, routing, and authorization

Implementation status

The following events are part of the documented contract. The current server build marks events as Implemented (registered socket routes) or Planned (contract defined, route not yet registered). Integrators should gate on the Implemented set; Planned events are documented so contracts evolve in a backward-compatible way.

Domain Implemented Planned
System system.ping system.pong is the response shape
Rooms room.join, room.leave —
Auction (client) auction.subscribe, auction.unsubscribe, auction.bid.place, auction.status.update, auction.pause, auction.resume, auction.end, auction.bid.mark, auction.winner.declare, auction.winner.record_lot, auction.announcement.create, auction.presence.floor.update auction.bid.cancel
Auction (server) auction.connected, auction.state.snapshot, auction.audience.snapshot, auction.audience.changed, auction.join_window.opened, auction.join_window.closed, auction.participation.joined (staff only), auction.state.changed, bid/prebid, announcement, and settings events earlier granular lifecycle names (auction.started, auction.paused, auction.closed, …) were collapsed into auction.state.changed
Auth (in-band) — auth.login, auth.logout (authentication is performed at the handshake)
Presence (client) auction.presence.floor.update (staff floor-presence toggle) —
Notification — notification.created, notification.read
Chat — chat.send, chat.typing

Each event page in events/ states its implementation status and, where names differ from the canonical server event, the mapping in Notes.