Skip to main content

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:
Operators cannot access each other’s queues or messages.

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.
Most integrations run comfortably on 1–2 connections with a handful of channels. If your architecture genuinely needs more (for example, you horizontally scale consumers across many Kubernetes pods, each with its own connection), contact your account manager — we can raise these limits per account after a short review.

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 priority hi; 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).
Key attributes:
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

When a market status changes to -1 (suspended), you must immediately stop accepting bets. Do not wait for settlement.

Bet Settlement

Published when markets are resulted.
Grade each bet from the (result, void_factor) pair — both are present on every settled outcome, so you never assume a default:
CS2 markets settle all-or-nothing, so today every outcome is either a clean win/loss (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.
Each market carries 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.
A rollback only reverses the result on our side; the original settlement already pulled the market from the player-facing offer. So every rollback is followed by an <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.
A void can only be reversed by <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.
change_type="5" does not mean “finished”. A match ending arrives as an <odds_change> carrying <sport_event_status status="4"> (see Sport-event status), not as a fixture change. Treating a change_type="5" coverage update as the end of a match — or expecting a finish on fixture_change — leaves finished matches open indefinitely. The only fixture_change that removes a match is change_type="3" (cancelled); everything else is metadata you can apply by re-fetching the fixture.

Alive

Heartbeat sent every 10 seconds per producer. Use it to validate feed connectivity. Discover producers through GET /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.
If you do not receive an <alive> message within 20 seconds, consider the connection stale. Suspend all markets and reconnect.

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:
  1. Diversity — each market comes from a different category (e.g. opening kills, bomb events, round dynamics) to give bettors variety.
  2. 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.
Recommended display pattern: show the first market in the message prominently and reveal subsequent markets as the bettor interacts. The feed’s ordering already reflects what to surface first — no custom sorting logic needed.

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.
Simplest integration path: take textEN (or textRU) directly from the message and display it to the end-user. No template engine, no catalogue look-up, no string interpolation — the text is ready to show as-is.Template path: for operators who prefer to build market names locally, fetch the catalogue once at integration time from GET /cs2/v1/markets/descriptions (or descriptions.xml for UOF-style XML) — every entry carries template and template_ru strings such as "{player} gets opening kill — Round {round}". Substitute the placeholders with the matching keys from the specifiers attribute on each live message:

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. Use aio_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 uses aio-pika (Python asyncio AMQP client):

Support

For credentials, connectivity issues, or integration questions: b2b@ticktock.bet
Last modified on June 21, 2026