Skip to content

Realtime overview

Auction-scoped activity at Handbid is broadcast over Socket.IO, keyed by auction GUID. Clients that want live updates connect to the Handbid Socket.IO server, join the rooms they care about, and listen for events.

Every realtime event is scoped to one auction. The room name is literally {auctionGuid}_v2. Once you’re in the room, you receive:

EventWhenPayload
event.bidA bid lands on an item in this auction.item id, amount, new winner
event.sealed_bidA sealed-bid offer is placed.item id, amount (masked for non-managers)
event.competitive_bidA competitive-bid offer is placed.item id, amount, identity
event.itemItem state changes (price, status, winner).item id, new state
event.soldItem closes with a winner.item id, winner id, sale price
event.timerAuction timer is set, paused, or expires.timer state
event.closeAuction or item closing event.what closed, when
event.promotedItem is promoted on a Handbid TV display.item id
event.broadcastManager broadcast to all attendees.text

Each event arrives as a JSON payload. The shape is stable; new fields may be added (additive), existing fields won’t change type or disappear.

Every V3 response that surfaces an auction returns its GUID. Use that value verbatim:

{
"id": 5,
"guid": "c1b41927-689f-4c20-8621-534e05ffbacc",
"name": "Annual Passholder Picks",
}

On item-shape responses (/v3/item/{id}) the field is named auctionGuid to keep the auction* prefix consistent with the surrounding fields.

The room name is {guid}_v2:

c1b41927-689f-4c20-8621-534e05ffbacc_v2

A small set of events is broadcast to a user room, not an auction room:

RoomEvents
user:{userGuid}event.user, event.winner, event.notification, event.favorite

user.guid is the recipient’s user GUID. The contract is: a client should join their own user room on connect, and rejoin auction rooms as the user navigates between screens.

The Socket.IO server is separate from the REST API. Production:

  • REST API: https://api-ha-prod-p8.handbid.dev
  • Socket.IO: https://node-ha-prod-p8.handbid.dev:3001 (path: /socket.io/)

The Socket.IO server emits events; it doesn’t accept commands. To “do something” you call REST. The REST mutation triggers a server-side event, which lands in the room a moment later.

The Socket.IO connection isn’t bearer-authenticated — Handbid’s realtime layer trusts any connected client to join any auction room. Sensitive fields (sealed-bid amounts, etc.) are masked server-side before broadcast, so room membership alone doesn’t leak private data.

If your integration has a use case for tighter realtime auth (per-user private rooms, signed event payloads, etc.), tell us.

connect()
on('connect', () =>
socket.emit('join', { room: 'user:' + currentUserGuid })
)
// when user opens an auction detail:
socket.emit('join', { room: auction.guid + '_v2' })
// when user leaves:
socket.emit('leave', { room: auction.guid + '_v2' })

Always leave rooms you’re no longer rendering. The server scales by trimming fanout — rooms with thousands of stale members are bad for everyone.

Bid storms at the end of a closing auction can produce hundreds of event.bid per minute. Coalesce on the client — at most 60fps re-renders, ideally 10fps for non-focused tabs. The events still arrive; you just don’t have to redraw on every one.

If your client’s Socket.IO connection drops and reconnects, re-fetch the state via REST before you trust subsequent events. A reconnected client missed N events while disconnected; the REST re-fetch resyncs.

  • Per-room presence (who else is here). Not surfaced.
  • Event history / replay. If you missed it, you missed it. Fetch via REST to catch up.
  • Server-side push to APNs/FCM. That’s a separate (Handbid-internal) delivery layer; you don’t subscribe to it from a Socket.IO room.