Skip to content

Auction Room Frontend Integration Guide

Use one WebSocket endpoint for every auction realtime role:

wss://api.example.com/ws

Subscribers, companies, and admins all connect to the same URL. The server authorizes each message from the authenticated JWT context.

Authentication

Native and mobile clients can send the token as an HTTP header:

Authorization: Bearer <access-token>

Browser clients usually cannot set custom WebSocket headers, so send the token through Sec-WebSocket-Protocol:

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

auction-room.v1 is also accepted for protocol compatibility.

Invalid or missing tokens close the socket with policy violation code 1008.

Message Contract

Every client message must be JSON:

{
  "id": "request-id",
  "type": "auction.subscribe",
  "version": "1.0",
  "source": "web-client",
  "timestamp": "2026-08-05T10:00:00.000Z",
  "data": {},
  "meta": {
    "traceId": "client-trace-id"
  }
}

Rules:

  • type is required and must match a registered event
  • id is optional but recommended for correlating acknowledgements and errors
  • data is validated per event
  • version, source, timestamp, meta are optional and informational
  • Use data, not the removed payload field

Every server frame adds three things the client must handle:

  • version — protocol version, always "1.0".
  • sequence — a server-assigned monotonic counter per connection. Track the last value you processed; a gap means frames were dropped and you should recover via snapshot + replay.
  • meta.traceId — server correlation id (echoes your meta.traceId when sent).

Successful command acknowledgements look like:

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

Errors look like:

{
  "id": "request-id",
  "type": "error",
  "version": "1.0",
  "source": "dichit-backend",
  "timestamp": "2026-08-05T10:00:00.000Z",
  "sequence": 6,
  "data": {
    "code": "FORBIDDEN",
    "message": "Safe client-facing message."
  }
}

Known error codes:

BAD_REQUEST
UNAUTHORIZED
FORBIDDEN
NOT_FOUND
CONFLICT
RATE_LIMITED
INTERNAL
  1. Open /ws with a valid access token.
  2. Send auction.subscribe for the active cycle.
  3. Wait for the initial server events.
  4. Render the latest auction.state.snapshot and auction.audience.snapshot.
  5. Send commands such as auction.bid.place.
  6. Apply pushed auction events such as auction.bid.created.
  7. Send auction.unsubscribe when leaving the screen.
  8. Reconnect and resubscribe after network loss.

Subscribe As Subscriber

Subscribers include enrolledSubscriberId:

{
  "id": "subscribe-001",
  "type": "auction.subscribe",
  "data": {
    "auctionId": "auction-id",
    "enrolledSubscriberId": "enrolled-subscriber-id"
  }
}

The server sends these initial events:

auction.connected
auction.state.snapshot
auction.audience.snapshot

It also returns an acknowledgement:

{
  "id": "subscribe-001",
  "type": "ack",
  "data": {
    "eventType": "auction.subscribe",
    "auctionId": "auction-id",
    "cycleId": "cycle-id",
    "room": "auction:auction-id"
  }
}

Subscribe As Company Or Admin

Company and superadmin users omit enrolledSubscriberId:

{
  "id": "subscribe-001",
  "type": "auction.subscribe",
  "data": {
    "auctionId": "auction-id"
  }
}

Staff snapshots can include staff-only auction state such as the full subscriber room view when authorized by the application use case.

Resume With Replay

Clients that persisted the last processed auction sequence can request replay data:

{
  "id": "subscribe-002",
  "type": "auction.subscribe",
  "data": {
    "auctionId": "auction-id",
    "enrolledSubscriberId": "enrolled-subscriber-id",
    "lastSequence": 42
  }
}

lastSequence is the auction-level cursor you last observed (data.serverSequence on auction.bid.created / auction.bid.updated events), not the connection envelope sequence. When replay is available, the server merges it into the auction.state.snapshot payload.

Place A Bid

Only subscriber connections can place live bids.

{
  "id": "bid-001",
  "type": "auction.bid.place",
  "data": {
    "auctionId": "auction-id",
    "enrolledSubscriberId": "enrolled-subscriber-id",
    "amountMinor": "2500000"
  }
}

Use decimal strings for money values to avoid JavaScript precision surprises. amountMinor is expressed in minor currency units. The envelope id doubles as the idempotency key — reuse it for retries of the same bid action.

auction.bid.place is synchronous: the server validates and commits against the latest locked auction state, then returns a terminal ack (accepted or replayed). Rejections return a correlated error. Pushed auction events still drive the shared room feed.

Successful acknowledgement:

{
  "id": "bid-001",
  "type": "ack",
  "version": "1.0",
  "source": "dichit-backend",
  "timestamp": "2026-08-05T10:00:00.000Z",
  "data": {
    "eventType": "auction.bid.place",
    "status": "accepted",
    "commandId": "bid-001",
    "auctionId": "auction-id",
    "cycleId": "cycle-id",
    "bidId": "bid-id",
    "amountMinor": "2500000",
    "stateVersion": 12
  }
}

The acknowledgement confirms that the bid is committed. Use the corresponding auction event to update the shared feed for every participant:

{
  "id": "evt_01HQ5BY3Y7Z2V6Y0W5X0W5X0W5X",
  "type": "auction.bid.created",
  "version": "1.0",
  "source": "auction-service",
  "timestamp": "2026-08-05T10:00:00.000Z",
  "sequence": 7,
  "data": {
    "auctionId": "auction-id",
    "cycleId": "cycle-id",
    "stateVersion": 7,
    "serverSequence": 12,
    "payload": {
      "bidId": "bid-id",
      "leadingBidAmount": 25000
    }
  }
}

A rejected bid (validation or authorization) returns command.rejected; a business-rule failure after acceptance (e.g. bid below current minimum) returns command.failed. Both carry correlationId: "bid-001" and an errorCode.

Unsubscribe

{
  "id": "unsubscribe-001",
  "type": "auction.unsubscribe",
  "data": {
    "auctionId": "auction-id"
  }
}

Successful acknowledgement:

{
  "id": "unsubscribe-001",
  "type": "ack",
  "data": {
    "eventType": "auction.unsubscribe",
    "auctionId": "auction-id",
    "unsubscribed": true
  }
}

Application Ping

The server also has protocol-level heartbeat pings. This event is for client application-level health checks.

{
  "id": "ping-001",
  "type": "system.ping",
  "data": {}
}

Acknowledgement data:

{
  "id": "ping-001",
  "type": "ack",
  "data": {
    "eventType": "system.ping",
    "type": "system.pong",
    "occurredAt": "2026-08-05T10:00:00.000Z"
  }
}

Generic Rooms

The frontend can join controlled generic rooms when needed. See events/room.md for the full room.join / room.leave request and response contract.

{
  "id": "room-join-001",
  "type": "room.join",
  "data": {
    "room": "user:user-id"
  }
}

Allowed room names:

notifications
admins
user:<current-user-id>
company:<current-company-id>

admins is only available to SUPERADMIN. company:<id> must match the authenticated user's canonical Company.id resolved from their active CompanyMembership.

[!WARNING] Attempting to join auction channels (auction:<auctionId> or auction:staff:<auctionId>) via room.join is forbidden and returns 403 Forbidden ("Use auction.subscribe to join an authorized auction channel."). Clients must use auction.subscribe to connect to auctions. Staff connections are automatically dual-joined to auction:<id> and auction:staff:<id> upon subscribing.

Leave a room:

{
  "id": "room-leave-001",
  "type": "room.leave",
  "data": {
    "room": "user:user-id"
  }
}

Reconnection Guidance

On socket close:

  • Open a new /ws connection with a fresh access token if needed
  • Send auction.subscribe again
  • Include lastSequence when the client has processed an auction-level sequence number (data.serverSequence)
  • Rebuild UI from the latest snapshot, then apply pushed events

If the access token expires, refresh it through the normal HTTP auth flow before opening a new socket.

Rate Limits

Default limit:

120 messages per 60 seconds

auction.bid.place limit:

100 messages per 60 seconds

Limits are applied per authenticated user, per IP, and per event type. A rate-limited message receives:

{
  "id": "bid-001",
  "type": "error",
  "data": {
    "code": "RATE_LIMITED",
    "message": "Too many WebSocket messages."
  }
}

WebSocket endpoint

All realtime auction traffic flows through the single /ws endpoint.