Subscriber Prebid — WebSocket Integration Guide¶
This document is the complete realtime contract for subscriber prebid in the auction flow, written for frontend teams integrating the Dichit auction room.
Prebid is split across two transports:
- REST — all prebid actions (place, edit, cancel) are HTTP calls on
/v2/subscribers/me/auctions/:auctionId/prebid…(see section 6). - WebSocket — the realtime stream on
/wspushes prebid lifecycle events and carries the full prebid state inside the auction snapshot.
The WebSocket does not create, update or cancel prebids. It tells the UI what changed while the REST API is the source of truth for the write result.
Reference: events/auction.md · events/presence.md · protocol.md · frontend-integration.md · errors.md · rate-limits.md
1. Prebid lifecycle overview¶
A cycle auction's prebid window is modelled by the derived prebidPhase on data.payload.auction (the persisted auctionStatus — PENDING/SCHEDULED/ READY/LIVE/PAUSED/ENDED/COMPLETE/CANCELLED — tracks live/end state, not the prebid window):
Phase (data.payload.auction.prebidPhase) | Meaning |
|---|---|
NONE | No prebid phase (no prebid window is open). |
OPEN | Subscribers can submit/cancel prebid. |
CLOSED | Prebid window closed; submissions no longer allowed. |
Key rules that gate prebid for a subscriber (all enforced server-side):
- The program bid type is auction-based and
hasPrebidis enabled. - The cycle is active and the prebid phase is
OPEN(nowbetweenprebidStartAtandprebidEndAt). - The subscriber has completed payment for that exact cycle.
- The subscriber has not already won once in the program.
- The subscriber has no active
PREBID-phase disqualification. - Only one active prebid per subscriber per auction exists.
MUTABLEpolicy → an existing active prebid is updated in place.IMMUTABLE_CANCEL_ONLYpolicy → update is rejected; the subscriber may only cancel (RESTDELETE) and then submit a new one.- If the program requires a prebid document, a valid
signatureAssetIdis required; the rendered PDF is generated asynchronously by the server.
Prebid status values: ACTIVE, CANCELLED, DISQUALIFIED, APPLIED, SUPERSEDED.
2. Connecting and authenticating¶
Single endpoint for every realtime role:
Browser clients pass the access token through Sec-WebSocket-Protocol:
Native/mobile clients may use the Authorization: Bearer <access-token> header. Invalid or missing tokens close the socket with code 1008.
Server heartbeat: protocol-level ping/pong plus the heartbeatIntervalMs reported in the auction.connected event. Send system.ping for application-level health checks.
3. Subscribe to the auction room¶
To receive prebid realtime state the subscriber subscribes to the auction room:
{
"id": "subscribe-prebid-001",
"type": "auction.subscribe",
"version": "1.0",
"source": "web-client",
"data": {
"auctionId": "auc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"enrolledSubscriberId": "enr_01HQ5BWYP1Y5RX0W5X0W5X"
}
}
enrolledSubscriberId is required for subscriber sockets. Optional data.lastSequence (the last observed auction-level data.serverSequence) requests replay of missed events, merged into the snapshot payload.
The server then sends, in order:
auction.connected— subscription confirmation +connectionIdandheartbeatIntervalMs.auction.state.snapshot— the full subscriber-scoped auction state, including all prebid fields (section 4).auction.audience.snapshot— current participants.- An
ackenvelope echoingeventType: "auction.subscribe"plusauctionId,cycleId,room.
Example ack:
{
"id": "subscribe-prebid-001",
"type": "ack",
"version": "1.0",
"source": "dichit-backend",
"timestamp": "2026-08-05T10:00:00.000Z",
"sequence": 1,
"data": {
"eventType": "auction.subscribe",
"auctionId": "auc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"cycleId": "cyc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"room": "auction:auc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X"
}
}
Leave the room with auction.unsubscribe (data.auctionId) when the screen is closed; this also removes the subscriber's online presence.
4. Prebid state inside auction.state.snapshot¶
The snapshot envelope:
{
"type": "auction.state.snapshot",
"id": "evt_01HQ5BY3Y7Z2V6Y0W5X0W5X0W5X",
"version": "1.0",
"source": "auction-service",
"timestamp": "2026-08-05T10:00:00.000Z",
"sequence": 2,
"data": {
"auctionId": "auc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"cycleId": "cyc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"payload": {
"auction": {},
"currentUser": {},
"viewerCount": 0,
"joinedParticipantCount": 0,
"onlineParticipantCount": 0,
"bidderCount": 0,
"recentBids": [],
"lastSequence": 0
}
}
}
data.payload.auction — prebid-relevant fields¶
| Field | Type | Description |
|---|---|---|
auctionId | string | Auction id. |
cycleId | string | Cycle id. |
prebidPhase | string | NONE | OPEN | CLOSED — the prebid phase from section 1. |
prebidStartAt | string | Prebid window open time (ISO-8601). |
prebidEndAt | string | Prebid window close time (ISO-8601). |
auctionStartAt | string | Scheduled live auction start (ISO-8601). |
auctionDurationSeconds | number | Live auction duration. |
activePrebidCount | number | Number of active prebids (0 outside LIVE/PAUSED/ENDED). |
minimumBidAmount | number | Floor for the bid amount. |
totalAmount | number | Cycle total amount. |
minimumBidReachedAt | string | When the minimum bid was reached (present after review). |
resolutionType | string | How the auction resolves (e.g. direct/lot/live). |
prebidAmountsRevealed | boolean | True after staff reveals completed-auction prebid amounts. |
prebidAmountsRevealedAt | string | null | Reveal timestamp, or null before reveal. |
settings | object | hasPrebid, prebidMutabilityPolicy, auctionBidMode, ... |
bids | array | Visible bids; prebid entries are hidden until reveal. |
data.payload.currentUser — the subscriber's own prebid state¶
| Field | Type | Description |
|---|---|---|
subscriberId | string | User id. |
enrolledSubscriberId | string | Enrollment id used for this cycle. |
isLeadingBidder | boolean | True if the subscriber currently leads the auction. |
isPaymentCompletedForCycle | boolean | Prebid gate: payment done for the cycle. |
hasAlreadyWonProgram | boolean | Prebid gate: already won once in this program. |
canPrebid | boolean | True when the cycle is active, the auction accepts prebids, the phase is OPEN, payment is complete, the subscriber has not won, there is no PREBID disqualification, and no active prebid exists. It does not validate a submitted amount or signature asset. |
prebidBlockedReason | string | null | Stable reason when canPrebid is false. |
isPrebidDisqualified | boolean | PREBID-specific disqualification state. |
isLiveBidDisqualified | boolean | LIVE-specific disqualification state; it does not independently block prebid. |
activePrebid | object | null | Current ACTIVE prebid (see shape below). |
latestPrebid | object | null | Most recent prebid regardless of status, incl. soft-deleted/disqualified (see shape below). |
latestBid | object | null | Latest live bid (present once bidding started). |
disqualifications | array | Bidder disqualifications (phase: "PREBID" blocks prebid). |
activePrebid / latestPrebid shape:
| Field | Type | Description |
|---|---|---|
id | string | Prebid id (pb_…). |
amount | number | Own prebid amount. |
status | string | ACTIVE/CANCELLED/DISQUALIFIED/APPLIED/SUPERSEDED. |
documentSubmissionId | string | null | Generated-document submission id (if required). |
documentSubmissionStatus | string | null | PENDING/QUEUED/GENERATED/… |
generatedPdfUrl | string | null | Rendered prebid document PDF URL. |
disqualifiedAt | string | null | Disqualification time. |
disqualificationReasonCode | string | null | KYC/payment/eligibility/rule reason. |
deletionReason | string | null | Soft-delete reason. |
deletedAt | string | null | Soft-delete time. |
createdAt / updatedAt | string | Timestamps. |
Frontend rule: render the prebid screen from currentUser.canPrebid, currentUser.prebidBlockedReason, currentUser.activePrebid and data.payload.auction.prebidPhase. Never let the UI decide eligibility — always trust the server fields.
5. Prebid-related server events¶
All server events use the standard envelope; auction domain events carry data.auctionId, data.cycleId, optional data.stateVersion, optional data.serverSequence, and data.payload.
5.1 auction.prebid.created¶
Published whenever a subscriber creates a new prebid. This event is durable, staff-scoped, and delivered through auction:staff:<auctionId>; subscriber sockets do not receive it. Updating an existing active prebid does not emit it.
| Payload field | Type | Description |
|---|---|---|
payload.id | string | New prebid id. |
payload.subscriberId | string | User id of the prebidder. |
payload.enrolledSubscriberId | string | Enrollment id of the prebidder. |
payload.bidderName | string | Staff display name. |
payload.amount | number | null | null while sealed. |
payload.createdAt | string | ISO-8601 creation time. |
payload.status | string | AuctionPrebidStatus value. |
payload.documentSubmissionId | string | null | Associated submission when a document exists. |
{
"type": "auction.prebid.created",
"id": "evt_01HQ5BY3Y7Z2V6Y0W5X0W5X0W5X",
"version": "1.0",
"source": "auction-service",
"timestamp": "2026-08-05T10:00:00.000Z",
"sequence": 7,
"data": {
"auctionId": "auc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"cycleId": "cyc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"serverSequence": 21,
"payload": {
"id": "pb_01HQ5BY3J7Z2V6Y0W5X0W5X0W5X",
"subscriberId": "sub_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"enrolledSubscriberId": "enr_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"bidderName": "Annie Mohan",
"amount": null,
"createdAt": "2026-08-05T10:00:00.000Z",
"status": "ACTIVE",
"documentSubmissionId": null
}
}
}
The event is persisted in the auction activity replay log and carries
data.serverSequence. Staff replay includes it; subscriber replay filters it out.amountremains nullable and is redacted while sealed.
Frontend handling: staff consoles may append/reconcile the prebid row using the payload and sequence. Subscriber screens must use the successful REST response plus snapshot/detail data for their own prebid state.
5.2 auction.prebid.deleted¶
Published by the staff/admin prebid delete path (DELETE /v2/admin/…/auctions/…/prebids/:prebidId or the auction.bid.mark/winner workflows that soft-delete a prebid). It carries targetUserId and is therefore delivered only to the affected subscriber (plus staff who see all events); other subscribers do not receive it.
Because the publish carries no payload, the socket adapter forwards the generic activity fallback, and fields with no value are omitted from the serialized frame (they are not sent as null):
| Field | Type | Description |
|---|---|---|
data.payload.metadata.prebidId | string | The deleted prebid id. |
data.payload.metadata.enrolledSubscriberId | string | Enrollment id of the affected subscriber. |
data.payload.bidId / data.payload.leadingBidAmount | — | Absent (undefined values are dropped on serialization). |
{
"type": "auction.prebid.deleted",
"id": "evt_01HQ5BY3Y7Z2V6Y0W5X0W5X0W5X",
"version": "1.0",
"source": "auction-service",
"timestamp": "2026-08-05T10:05:00.000Z",
"sequence": 9,
"data": {
"auctionId": "auc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"cycleId": "cyc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"stateVersion": 4,
"payload": {
"metadata": {
"prebidId": "pb_01HQ5BY3J7Z2V6Y0W5X0W5X0W5X",
"enrolledSubscriberId": "enr_01HQ5BWYP1Y5RX0W5X0W5X0W5X"
}
}
}
}
Frontend handling: on metadata.prebidId matching the locally held active prebid, clear currentUser.activePrebid / the prebid form, and re-evaluate canPrebid (re-fetch the snapshot or the auction detail for exactness).
Note: the subscriber's own REST cancel (
DELETE /v2/subscribers/me/auctions/:auctionId/prebid/:prebidId?enrolledSubscriberId=…) does not publish a realtime event — the REST response (status: "CANCELLED") is the source of truth for that action.
5.3 auction.state.changed — prebid-window transitions¶
Schedule changes (e.g. the prebid window opening/closing, or the auction going live) are delivered as auction.state.changed with data.payload.transition. The ones that matter for prebid:
transition | When it fires | Payload extras |
|---|---|---|
schedule_changed | Prebid window opened/closed or schedule updated | prebidStartAt, prebidEndAt… |
started | Auction went live — prebid window is over | status, auctionEndAt |
winner_selected | A winner was declared (possibly from a prebid) | bidId/leadingBidAmount |
prebids_revealed | Completed-auction prebid amounts were revealed | prebidAmountsRevealed |
{
"type": "auction.state.changed",
"version": "1.0",
"source": "auction-service",
"timestamp": "2026-08-05T09:00:00.000Z",
"sequence": 3,
"data": {
"auctionId": "auc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"cycleId": "cyc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"stateVersion": 2,
"payload": {
"transition": "schedule_changed",
"prebidStartAt": "2026-08-05T09:00:00.000Z",
"prebidEndAt": "2026-08-06T09:00:00.000Z"
}
}
}
Frontend handling: recompute the countdown, the submit/cancel button state, and switch the screen between prebid and live modes off data.payload.auction.prebidPhase (re-fetch auction.state.snapshot when needed).
5.4 auction.bidder.disqualified (prebid phase)¶
If a subscriber is disqualified from prebidding (phase: "PREBID"), the room receives auction.bidder.disqualified; the affected subscriber should treat an in-flight or submitted prebid as void and re-render the form as locked.
{
"type": "auction.bidder.disqualified",
"version": "1.0",
"source": "auction-service",
"timestamp": "2026-08-05T10:05:00.000Z",
"sequence": 10,
"data": {
"auctionId": "auc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"cycleId": "cyc_01HQ5BWNJ5Y1P5RX0W5X0W5X0W5X",
"stateVersion": 5,
"payload": {
"subscriberId": "enr_01HQ5BWYP1Y5RX0W5X0W5X0W5X",
"reason": "KYC_ISSUE"
}
}
}
6. REST endpoints (the write path)¶
The WebSocket only streams state; prebid writes go through REST. All endpoints are scoped to the authenticated SUBSCRIBER and require :auctionId plus the enrolledSubscriberId of the enrollment being acted on (body for POST/PUT, query for DELETE).
| Method | Path | Purpose |
|---|---|---|
POST | /v2/subscribers/me/auctions/:auctionId/prebid | Place a new active prebid for the enrollment. |
PUT | /v2/subscribers/me/auctions/:auctionId/prebid/:prebidId | Edit the active prebid (MUTABLE_UNTIL_CLOSE only). |
DELETE | /v2/subscribers/me/auctions/:auctionId/prebid/:prebidId?enrolledSubscriberId=… | Cancel the active prebid (both policies). |
Prebid state is read from the auction detail response (GET /v2/subscribers/me/auctions/:auctionId) — latestPrebid and enrollments[].prebids[] — there is no dedicated prebid GET endpoint.
POST / PUT body¶
| Field | Type | Required | Description |
|---|---|---|---|
enrolledSubscriberId | string | Yes | Enrollment the prebid belongs to. |
amount | number | Yes | Prebid amount (decimal accepted). |
signatureAssetId | string | No | Required only when the program demands a prebid document. |
POST / PUT / DELETE response (data)¶
| Field | Type | Description |
|---|---|---|
auctionId | string | Auction id. |
cycleId | string | Cycle id. |
prebidId | string | Created/updated/cancelled prebid id. |
enrolledSubscriberId | string | Enrollment the prebid belongs to. |
status | string | ACTIVE (POST/PUT) / CANCELLED (DELETE). |
documentSubmissionId | string | null | Document submission id, when a prebid document is generated. |
Business-rule errors (HTTP 400 / 403 / 404)¶
| Message (abridged) | Meaning |
|---|---|
| Prebid window not open / auction started | Place/edit/cancel outside the OPEN prebid phase. |
| You already have an active prebid for this auction | An active prebid exists for this enrollment. |
| Payment not completed for cycle | Cycle invoice unpaid. |
| Already won in this program | One-win-per-program rule. |
| Prebid cannot be updated after submission | IMMUTABLE_CANCEL_ONLY policy. |
| Active prebid not found | Prebid id unknown or not owned by the subscriber/enrollment. |
| A signature is required / template missing | Prebid document not satisfiable. |
| Bid amount outside allowed range | Amount validation (minimumBidAmount…). |
7. Recommended integration flow¶
- Open
/wswith the access token. auction.subscribewithauctionId(fetch via REST; auction row guaranteed beforeREADY/LIVE) +enrolledSubscriberId(andlastSequencewhen reconnecting).- On
auction.state.snapshot: - Render phase from
data.payload.auction.prebidPhase. - Render the prebid form/banner from
data.payload.currentUser.canPrebid,activePrebid,latestPrebid, and the document gates. - Show
activePrebidCountfromdata.payload.auction.activePrebidCount. - When the user submits: call REST
POST /v2/subscribers/me/auctions/:auctionId/prebidwithenrolledSubscriberId+amount. - Update the UI from the REST response (
prebidId,status). - Do not await
auction.prebid.created; it is staff-only. Re-read the auction detail response when reconciliation is required. - When the user cancels: call REST
DELETE /v2/subscribers/me/auctions/:auctionId/prebid/:prebidId?enrolledSubscriberId=…(allowed under both mutability policies). The REST response is the source of truth; no event is pushed. - Apply
auction.state.changedtransitions to switch prebid ⇄ live modes and update countdowns. - Handle
auction.bidder.disqualified(prebid phase) by locking the form. auction.unsubscribewhen leaving the screen.- On reconnect: resubscribe with
lastSequence, rebuild from the fresh snapshot, then apply pushed events.
8. Sequencing and reconnection¶
data.serverSequenceon bid-activity events (auction.bid.created,auction.bid.updated, …) is the auction-level cursor. Persist it and pass it back aslastSequenceon resubscribe to replay anything missed. Prebid events are not persisted in the replay log and carry noserverSequence; after a gap, re-read own prebid state from the auction detail response or the fresh snapshot.- The envelope-level
sequenceis per-connection only; a gap means dropped frames → resubscribe withlastSequence. - Replay events are merged into the snapshot payload as
data.payload.replaywhenlastSequenceis provided.
9. Rate limits¶
| Scope | Limit |
|---|---|
| Default (incl. subscribe) | 120 messages / 60 s |
auction.bid.place | 100 messages / 60 s |
Exceeding a limit returns an error envelope with code: "RATE_LIMITED".
Related documents¶
- events/auction.md — full auction room contract (bids, staff commands, announcements).
- events/presence.md — presence stream inside the auction room.
- frontend-integration.md — general client flow and message contract.
- protocol.md — envelope, async CQRS, sequencing.
- errors.md — error codes.