Overview
The Ticktock Messaging Feed delivers real-time market updates through AMQP 0-9-1 (RabbitMQ). Messages are lightweight XML payloads containing only changed data — odds, settlements, and fixture changes. The REST API complements the feed for recovery and static data.Architecture
Connection
Each B2B operator gets an isolated RabbitMQ vhost and a dedicated user:Getting Credentials
Contact your account manager or email b2b@ticktock.bet to request AMQP access provisioning. Credentials are generated via the admin panel and returned once — store them immediately.Connection Limits
Each B2B account is provisioned with the following resource limits per environment (Production and Integration are counted separately):
Use AMQP channels to multiplex multiple consumers/subscriptions over a single TCP connection — channels are cheap (one Erlang process each) and a well-designed integration typically needs one or two connections even at scale. Opening many short-lived connections is an anti-pattern: it strains both client and broker, and you will hit the cap quickly.
Routing Keys
Messages use an 8-section topic routing key. The layout is identical for every message type and every sport — split on. and read fixed positions;
empty fields are the literal placeholder - so every key has exactly 8
sections:
Examples
Binding Patterns
Use wildcards# (multi-section) and * (single section) to filter messages:
Message Types
The feed publishes exactly these 8 message types — no more. A type outside the set has no handler on the client and would stall its stream, so the set is closed. Priority is determined by phase, not by message type. Every live message — of any type — uses priorityhi; every recovery republish uses
lo (see Recovery). So you can bind hi.… for live and lo.…
for recovery and never split a single message type across both. The Live priority column below is what each type carries during normal live operation.
There is no
bet_stop message type. A full-match suspension at a key moment
(goal, red card) is expressed as status="-1" on the affected <market>
elements inside an <odds_change> (one message can carry several <market>
children) — not as a separate message.<rollback_bet_cancel> carries the same {id, specifiers} market shape as
<rollback_bet_settlement>:
Match Identification
Every match-scoped message carries two identifiers:Odds Change
Published when markets are created or odds update. The message is a delta — it contains only changed markets. Markets not present in the message remain unchanged. Every<odds_change> (live and prematch) carries a <sport_event_status> element as its first child — its status attribute is the single driver of the match lifecycle (prematch → live → finished/removed; see Sport-event status). A message without it would leave the match frozen in prematch.
Sport-event status (BB 4.1)
<sport_event_status> is the only signal you need to drive a match’s
lifecycle. It rides every <odds_change> (live and prematch). The status
attribute is an integer code from a fixed four-value set — read it as a
number, not a string. Codes outside the set are not emitted, and legacy
match-level string names (upcoming, finished, …) never appear here.
A mid-match pause (e.g. the break between maps in a best-of series) stays
1 (live) — the match is in progress, it does not revert to 0.
Match finish is status="4" here, carried on a status-only <odds_change>
(no <odds> child) with the final home_score / away_score / winner_id —
not a <fixture_change>. See Fixture Change for why
change_type="5" is coverage-only.
Don’t rely on <fixture_change> alone to advance the match — a match created in
the default prematch state never goes live or finishes without a
<sport_event_status> on its <odds_change> stream.
CS2 publishes only
{0, 1, 4, 5}. The remaining UOF EventStatus
codes — 2 suspended, 3 ended, 6 delayed, 7 interrupted, 8 postponed,
9 abandoned — are for sports whose lifecycle the four-value set can’t express.
Football emits the full set today (see Football → Match status &
lifecycle); each sport’s descriptions/{locale}/match_status
enumerates exactly the codes it publishes. A CS2-only integration can ignore the
extras. The key distinctions they add: a suspended / interrupted match is
paused, not dropped; a postponed / delayed match is rescheduled, not
removed; and ended (play over) is distinct from closed (settled & finalized).Primary integration contract:
market.id (stable integer type code) + specifiers uniquely identifies a market type within a match context. market.tt_market_id is a unique UUID for each individual market instance — use it for bet placement and settlement correlation. textEN / textRU is the recommended way to display markets. The type and specifiers attributes are optional convenience fields for operators who want to build custom UIs or map markets programmatically. The specifiers attribute always includes map=N and round=N; it adds player=Name for player-scoped markets, and competitor=Team for team-scoped economy markets. You can safely ignore them.Instance identity is stable (BB 3.5). For the life of a market instance — across price moves, suspension/resume, and recovery replays — both the outcome id (always
1 for CS2 single-outcome markets) and the specifier set are fixed. They identify the instance, so they never change once first published; only odds/status move. The in-game map name is deliberately not a specifier (it is nullable and would otherwise appear mid-life and mutate the identity) — read it from the fixture/summary per-map data instead. So at T=0 and T=10s the same market is id=1042 specifiers="map=1\|round=5" with outcome id="1", only the odds differing.Specifier value form (BB 3.6). For CS2, the
player and competitor specifier values are the raw display name (e.g. player=s1mple, competitor=Navi) — variable_text in the catalog. The client renders them as-is; for CS2 this is acceptable (CS2 entities aren’t translated or renamed). For a future football integration, team/player specifiers would instead carry a stable id (a competitor/player URN) that the client resolves to a per-locale, rename-stable name — or stay raw strings, accepting no localization and stale names on rename. That choice is part of the football build (see follow-up F-6); CS2 stays raw.Market Status Codes
Bet Settlement
Published when markets are resulted.(result, void_factor) pair — both are present on
every settled outcome, so you never assume a default:
void_factor="0") or a full void ("1.0"). The fractional form is
part of the contract for partial voids (e.g. a future football half-result):
the exact payout formula is fixed and per-market, so a 0 < void_factor < 1
value is unambiguous when it appears.
A settled market lists every one of its active outcomes — the winner and
each loser — so the losing bets actually receive a status and settle. CS2
markets are single-outcome propositions (one <outcome id="1">); a
multi-outcome market lists them all:
When a market is settled, it is automatically removed from all subsequent
<odds_change> messages.Bet Cancel
Published when a market is voided — return all stakes.void_factor so the same (result, void_factor) payout
rule as <bet_settlement> applies: a cancel is a full void (void_factor="1.0"
→ return all stakes); a partial cancel carries a fraction 0 < void_factor < 1.
Each market also carries a numeric void_reason_id — decode it into a
human-readable name and description from the catalog at
GET /v1/descriptions/{locale}/void_reasons
(pull it once and store the map). The reason reflects the real cause (e.g. an
admin match cancel, an odds error), not a single default. (void_reason_params
is supplementary free text and need not be parsed.)
<bet_cancel> may also carry an optional start_time / end_time refund
window (unix-ms, on the root). When present, refund only bets placed inside
the window — a way to void a period of bad odds without cancelling legitimate
bets accepted outside it. When absent, refund every bet on the market (the
default; CS2 micro-markets always void whole-market).
Rollback Bet Settlement
Reverses a previous settlement due to an error. Rare — you will also be notified via your support channel.<odds_change> that re-lists the affected market with live
odds (status="1") — that follow-up message is what puts it back in front of
bettors:
Rollback Bet Cancel
Reverses a previous<bet_cancel> — the market was voided in error, so the
refund is undone and the market becomes valid again. Carries the same
{id, specifiers} market shape as <rollback_bet_settlement>. Rare; you will
also be notified via your support channel.
<rollback_bet_cancel> — <rollback_bet_settlement>
reverses a prior settlement, never a cancel, so without this message a
mistaken void would be permanent. As with a settlement rollback, the market is
put back in front of bettors by the follow-up <odds_change> that re-lists
it with live odds (the rollback only reverses the result on our side).
If the original <bet_cancel> carried a start_time / end_time refund
window, the <rollback_bet_cancel> carries the same window, so the reversal
applies to exactly the bets that were voided.
Fixture Change
Notifies about match lifecycle events. Re-fetch the fixture from the REST API when you receive this message.Alive
Heartbeat sent every 10 seconds per producer. Use it to validate feed connectivity. Discover producers throughGET /v1/descriptions/producers before starting
monitoring. The catalog’s producer.id is identical to the product
attribute on every message from that producer, including recovery messages.
Market Ordering
Each<odds_change> message delivers up to 3 markets per round, already sorted in the recommended display order.
The feed applies two selection constraints:
- Diversity — each market comes from a different category (e.g. opening kills, bomb events, round dynamics) to give bettors variety.
- Timing order — markets are ordered earliest-resolving first. The first market in the message resolves soonest in the round, the last resolves latest. This minimises risk from feed-delivery delays.
Localisation
Every<market> element carries both English and Russian descriptions:
Outcome elements carry
active, odds, and probabilities attributes — localised text is on the parent <market> element only.
Reconnection and recovery
RabbitMQ persistent queues retain messages while your consumer is disconnected. Messages delivered to a durable queue are not lost if your service restarts — they will be re-delivered when you reconnect. Useaio_pika.connect_robust (or equivalent in your AMQP client) for automatic reconnection with exponential backoff.
After reconnecting, call POST /cs2/v1/recovery/initiate_request with the timestamp of the last message you processed and a positive integer request_id. The server republishes the current state of every active market plus settlements and voids that happened since after. Every recovery XML root carries that exact request_id, including the terminating <snapshot_complete>. Use the XML attribute for live/recovery classification and correlation. The recovery=1 header remains available as supplementary metadata. Recovery routing keys use priority lo and echo the optional positive integer node_id in their final section.
Code Example
The following example usesaio-pika (Python asyncio AMQP client):