4.1 Offers Stream
Endpoint:
GET /ws/offers
This endpoint upgrades to a WebSocket connection. Once connected, MollyBet receives real-time offer messages from you. Traffic is one-way: we send nothing but protocol-level pings.
Contents:
- 4.1.1 Connecting
- 4.1.2 Message envelope
- 4.1.3 SelectionPrices
- 4.1.4 DeleteEventMarket
- 4.1.5 DeleteEvent
- 4.1.6 Error
4.1.1 Connecting
We connect over wss:// and authenticate with the same HTTP Basic Authorization header used on the REST endpoints. The full URL is agreed with you during onboarding.
Two connection-level rules matter:
- Send text frames only. Every message must be a UTF-8 JSON text frame. A binary frame — or any other non-text frame — is treated as a fatal protocol error and we drop the connection.
- Expect pings. We enable a WebSocket heartbeat with an 8 second interval. Your server must answer protocol-level ping frames with pongs, which any standard WebSocket library does automatically.
4.1.2 Message envelope
Every message on the stream is wrapped in the same envelope. There is no bare payload.
ts [ string ]:
When the message was generated, in UTC ISO 8601.
type [ string ]:
Which kind of message this is. One of SelectionPrices, DeleteEventMarket, DeleteEvent or Error. Note the exact casing.
payload [ object ]:
The body, whose shape is determined by type.
{
"ts": "2024-04-13T16:30:00.000Z",
"type": "SelectionPrices",
"payload": { }
}
type and payload are validated together — a payload whose shape does not match its type is rejected:
type | payload shape |
|---|---|
SelectionPrices | A full event, exactly as returned by /events, plus inrunning. |
DeleteEventMarket | event_id and market_id. |
DeleteEvent | event_id only. |
Error | message and details. |
4.1.3 SelectionPrices
Publishes prices for one or more selections. The payload is a complete event object — the same normal event or special event shape
used by /events, carrying sport, league, start_time, home and away (or runners and event for
a multirunner), and offers.
Because the payload is a whole event, this message can introduce an event we have not seen before as well as update one we already know about.
inrunning [ boolean ] (required):
Whether the event is currently in-play. This field is optional on the REST `/events` response, where we infer it from the type you were polled for — but over the WebSocket there is no such context, so it is mandatory here. A message whose inrunning is null or absent is rejected and the connection is dropped.
offers [ array ]:
The selections being updated. Each entry is applied independently, so a message may carry more than one, though normally you send only the selection that changed.
An offer's quotes list replaces whatever we currently hold for that selection:
- A non-empty list becomes the prices and liquidity you are standing behind right now.
- Sending fewer quotes than before removes the ones you left out.
- An empty list removes all prices for that selection, suspending it without withdrawing the market.
Example — updating a price:
{
"ts": "2024-04-13T16:30:00.000Z",
"type": "SelectionPrices",
"payload": {
"sport": "fb",
"league": {"name": "Spain | La Liga", "id": 123},
"start_time": "2024-04-13T16:30:00.000Z",
"home": {"name": "FC Barcelona", "id": 456},
"away": {"name": "Real Madrid", "id": 789},
"inrunning": false,
"offers": [
{
"bet_type": "for,ah,h,-10",
"selection_id": "123|28276122|456|for",
"quotes": [
{"price": 2.345, "min_stake": ["EUR", 1], "max_stake": ["EUR", 500]}
]
}
]
}
}
Example — suspending a selection:
{
"ts": "2024-04-13T16:30:00.000Z",
"type": "SelectionPrices",
"payload": {
"sport": "fb",
"league": {"name": "Spain | La Liga", "id": 123},
"start_time": "2024-04-13T16:30:00.000Z",
"home": {"name": "FC Barcelona", "id": 456},
"away": {"name": "Real Madrid", "id": 789},
"inrunning": false,
"offers": [
{
"bet_type": "for,ah,h,-10",
"selection_id": "123|28276122|456|for",
"quotes": []
}
]
}
}
4.1.4 DeleteEventMarket
Withdraws a single market from an event, along with every selection in it.
event_id [ string ]:
The event, identified by the first segment of the selection IDs you publish for it.
market_id [ string ]:
The market to remove, identified by the second segment of those selection IDs.
Both fields are required for this message type. See A note on identifiers.
{
"ts": "2024-04-13T16:30:00.000Z",
"type": "DeleteEventMarket",
"payload": {
"event_id": "123",
"market_id": "28276122"
}
}
4.1.5 DeleteEvent
Withdraws an entire event and every market on it.
event_id [ string ]:
The event to remove, identified by the first segment of the selection IDs you publish for it.
Send event_id alone. The presence or absence of market_id is what distinguishes this message from
DeleteEventMarket, so do not include it.
{
"ts": "2024-04-13T16:30:00.000Z",
"type": "DeleteEvent",
"payload": {
"event_id": "123"
}
}
4.1.6 Error
Reports that something has gone wrong on your side.
message [ string ]:
What went wrong.
details [ any ]:
Any supporting detail. Free-form — an object, a string, or null.
{
"ts": "2024-04-13T16:30:00.000Z",
"type": "Error",
"payload": {
"message": "Upstream feed unavailable",
"details": null
}
}
caution
An Error message terminates the session. MollyBet logs it, treats the connection as no longer
authenticated, and disconnects. It is not a warning channel — do not use it to report recoverable or
per-selection problems.
To withdraw prices, send SelectionPrices with an empty quotes list, or a
delete message. Reserve Error for a genuine session-level failure.
4.1.7 A note on identifiers
The delete messages address events and markets by the segments of the selection ID, whose format is:
<event_id>|<market_id>|<selection_id>|<bet_side>
So a selection ID of 123|28276122|456|for has event ID 123 and market ID 28276122 — the values used in
the delete examples above. This is the reason the selection ID format matters even if you never parse it
yourself: it is what makes targeted withdrawal possible, and we split on | to route every offer you
publish.