Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.ticktock.bet/llms.txt

Use this file to discover all available pages before exploring further.

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.
PropertyValue
ProtocolAMQP 0-9-1 (RabbitMQ)
PayloadXML
Exchangettfeed (topic)
Heartbeat<alive> every 10 seconds
LocalisationEnglish (textEN) + Russian (textRU)
Product2 (live)

Architecture

Connection

Each B2B operator gets an isolated RabbitMQ vhost and a dedicated user:
PropertyValue
Servermq.ticktock.bet
Port5672 (AMQP) / 5671 (AMQPS)
Virtual Host/<org-slug> (e.g. /acme-sports)
Usernameop-<org-slug> (e.g. op-acme-sports)
Exchangettfeed
amqp://op-<slug>:<password>@mq.ticktock.bet:5672/<slug>
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):
ResourceLimitNotes
Connections10concurrent AMQP connections across all consumers
Channels200total channels across all your connections (= 20 channels per connection on average)
Queues10total queues declared in your vhost
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:
{priority}.{prematch}.{live}.{msg_type}.{sport}.{source}:{entity}.{event_id}.{node_id}
SectionDescriptionExample values
priorityProcessing priorityhi / lo / -
prematchPre-match flagpre / -
liveLive flaglive / -
msg_typeMessage typeodds_change, bet_settlement, alive
sportSport ID3 (CS2)
source:entityEvent type URNod:match
event_idMatch number2588141
node_idRecovery node-

Examples

MessageRouting Key
Odds change for match 2588141hi.-.live.odds_change.3.od:match.2588141.-
Bet settlement for match 2588141hi.-.live.bet_settlement.3.od:match.2588141.-
Fixture changehi.-.live.fixture_change.3.od:match.2588141.-
Recovery odds replay (priority lo, optional node_id)lo.-.live.odds_change.3.od:match.2588141.nodeA
Recovery <snapshot_complete>-.-.-.snapshot_complete.-.-.-.nodeA
Alive heartbeat-.-.-.alive.-.-.-.-

Binding Patterns

Use wildcards # (multi-section) and * (single section) to filter messages:
# All live messages (odds + settlements + fixture changes)
hi.-.live.#

# Only odds changes
hi.-.live.odds_change.#

# All messages for a specific match
hi.-.live.*.3.od:match.2588141.-

# Everything including alive heartbeats
#

Message Types

XML ElementRouting SegmentPriorityDescription
<odds_change>odds_changehighNew or updated market odds
<bet_settlement>bet_settlementlowMarkets settled — grade bets
<bet_cancel>bet_cancellowMarkets voided — return stakes
<rollback_bet_settlement>rollback_bet_settlementlowPrevious settlement reversed
<fixture_change>fixture_changehighMatch started, cancelled, or finished
<alive>alive-Heartbeat — every 10 seconds
<snapshot_complete>snapshot_complete-Initial state delivery complete after subscription

Match Identification

Every match-scoped message carries two identifiers:
AttributeFormatPurpose
event_idod:match:{number}Oddin match ID — stable external identifier for cross-referencing
tt_match_idUUIDInternal match UUID — use for REST API lookups and settlement queries

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.
<odds_change product="2" timestamp="1711234567890"
             event_id="od:match:2588141"
             tt_match_id="98deb7ef-def9-47d4-854c-a13618952f99">
  <odds>
    <market id="1001"
            tt_market_id="a3f1b2c4-5678-4d9e-b012-abcdef123456"
            type="headshot_opening"
            specifiers="map=1|round=5"
            status="1"
            textEN="First kill is a headshot — Round 5"
            textRU="Первое убийство хедшотом — Раунд 5">
      <outcome id="1" active="1" odds="2.10" probabilities="0.41000" />
    </market>
    <market id="1013"
            tt_market_id="b7e2c3d5-6789-4e0f-c123-bcdef2345678"
            type="grenade_kill"
            specifiers="map=1|round=5"
            status="1"
            textEN="Grenade kill in Round 5"
            textRU="Убийство гранатой в раунде 5">
      <outcome id="1" active="1" odds="7.40" probabilities="0.10000" />
    </market>
    <market id="1050"
            tt_market_id="c8f3d4e6-7890-4f1a-d234-cdef34567890"
            type="bomb_planted"
            specifiers="map=1|round=5"
            status="1"
            textEN="Bomb planted — Round 5"
            textRU="Бомба заложена — Раунд 5">
      <outcome id="1" active="1" odds="1.85" probabilities="0.47000" />
    </market>
  </odds>
</odds_change>
Key attributes:
AttributeDescription
market.idStable integer market type code (1001–1091)
market.tt_market_idUnique UUID for this specific market instance — use for bet placement, settlement correlation, and audit
market.statusMarket status code (see table below)
market.typeMarket type identifier (e.g. grenade_kill, headshot_opening)
market.specifiersPipe-separated context keys. Keys: map=N (integer map number), map_name=Mirage (in-game map name, when known), round=N (round number), player=Name (player-scoped markets), competitor=Team (team-scoped markets — eco_upset, eco_frags, force_buy_win). Example: map=1|map_name=Mirage|round=5|player=s1mple. Present on every message type: odds_change, bet_settlement, bet_cancel, rollback.
market.textEN / market.textRUHuman-readable market description — display directly to end-users
outcome.active1 = active, 0 = deactivated
outcome.oddsDecimal odds for this outcome
outcome.probabilitiesImplied probability (e.g. 0.41000)
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 map_name=Mirage when the map name is known, player=Name for player-scoped markets, and competitor=Team for team-scoped economy markets. You can safely ignore them.

Market Status Codes

CodeStatusDescription
1ActiveBets can be accepted, odds are flowing
0DeactivatedOdds not shown, bets not accepted
-1SuspendedStop accepting bets immediately
-3SettledResult known — only in <bet_settlement> messages
-4CancelledMarket voided — return stakes — only in <bet_cancel> messages
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.
<bet_settlement product="2" timestamp="1711234590123"
                event_id="od:match:2588141"
                tt_match_id="98deb7ef-def9-47d4-854c-a13618952f99">
  <outcomes>
    <market id="1013"
            tt_market_id="b7e2c3d5-6789-4e0f-c123-bcdef2345678"
            type="grenade_kill"
            specifiers="map=1|round=5"
            status="-3"
            textEN="Grenade kill in Round 5"
            textRU="Убийство гранатой в раунде 5">
      <outcome id="1" result="1"/>
    </market>
  </outcomes>
</bet_settlement>
result valueMeaning
1Winning outcome — pay out
0Losing outcome
If a market is voided via settlement (e.g. match cancellation), the outcome may include void_factor="1.0" — return the full stake to the bettor:
<outcome id="1" result="0" void_factor="1.0"/>
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.
<bet_cancel product="2" timestamp="1711234600000"
            event_id="od:match:2588141"
            tt_match_id="98deb7ef-def9-47d4-854c-a13618952f99">
  <market id="1013"
          tt_market_id="b7e2c3d5-6789-4e0f-c123-bcdef2345678"
          type="grenade_kill"
          specifiers="map=1|round=5"
          void_reason_id="4"
          void_reason_params="custom=not_played"
          textEN="Grenade kill in Round 5"
          textRU="Убийство гранатой в раунде 5" />
</bet_cancel>

Rollback Bet Settlement

Reverses a previous settlement due to an error. Rare — you will also be notified via your support channel.
<rollback_bet_settlement product="2" timestamp="1711234610000"
                         event_id="od:match:2588141"
                         tt_match_id="98deb7ef-def9-47d4-854c-a13618952f99">
  <market id="1013"
          tt_market_id="b7e2c3d5-6789-4e0f-c123-bcdef2345678"
          type="grenade_kill"
          specifiers="map=1|round=5" />
</rollback_bet_settlement>

Fixture Change

Notifies about match lifecycle events. Re-fetch the fixture from the REST API when you receive this message.
<fixture_change product="2" timestamp="1711234500000"
                event_id="od:match:2588141"
                tt_match_id="98deb7ef-def9-47d4-854c-a13618952f99"
                change_type="1"/>
change_typeMeaning
1New fixture with markets
2Start time changed
3Match cancelled
5Coverage changed / match finished

Alive

Heartbeat sent every 10 seconds per producer. Use it to validate feed connectivity.
<alive product="2" timestamp="1711234570000" subscribed="1"/>
subscribedMeaning
1Normal — feed is healthy
0Error or producer restart — suspend all markets and initiate recovery
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.
Position in messageResolvesTypical categories
FirstEarly in roundOpening kills, weapon kills
SecondMid-roundMulti-kills, round dynamics, highlight kills
ThirdLate in roundBomb events, economy, flawless
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:
AttributeLanguageExample
textENEnglish”Grenade kill in Round 4”
textRURussian”Убийство гранатой в раунде 4”
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 Oddin-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:
PlaceholderSpecifierNotes
{round}roundalways present
{player}playerplayer-scoped markets (player_opening, awp_player_kill, star_multi_kill)
{competitor}competitorteam-scoped economy markets (eco_upset, eco_frags, force_buy_win)
{map_name}map_namereserved for future map-scoped templates; the specifier itself is already populated when the map name is known

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. The server republishes the current state of every active market plus settlements and voids that happened since after, ending with a <snapshot_complete request_id="…"> message so you know you’re caught up. Recovery messages reuse the same exchange as the live feed, carry a recovery=1 header, and use routing-key priority lo (vs live hi) — you can bind a separate queue if you want to route them differently.

Code Example

The following example uses aio-pika (Python asyncio AMQP client):
import asyncio
import aio_pika
from xml.etree import ElementTree


AMQP_URL = "amqp://op-acme-sports:<password>@mq.ticktock.bet:5672/acme-sports"
EXCHANGE = "ttfeed"


async def main():
    connection = await aio_pika.connect_robust(AMQP_URL)
    channel = await connection.channel()

    exchange = await channel.declare_exchange(
        EXCHANGE, aio_pika.ExchangeType.TOPIC, durable=True, passive=True,
    )

    queue = await channel.declare_queue("ticktock-all", durable=True)
    await queue.bind(exchange, routing_key="hi.-.live.#")
    await queue.bind(exchange, routing_key="-.-.-.alive.-.-.-.-")

    async with queue.iterator() as q:
        async for message in q:
            async with message.process():
                xml_body = message.body.decode()
                routing_key = message.routing_key
                # Extract message type from 4th section of routing key
                msg_type = routing_key.split(".")[3] if routing_key else "unknown"

                if msg_type == "odds_change":
                    root = ElementTree.fromstring(xml_body)
                    event_id = root.get("event_id")
                    for market in root.findall(".//market"):
                        status = market.get("status")
                        text = market.get("textEN")
                        if status == "-1":
                            suspend_market(market.get("id"))
                        elif status == "1":
                            for outcome in market.findall("outcome"):
                                update_odds(market.get("id"), text, outcome.get("odds"))

                elif msg_type == "bet_settlement":
                    root = ElementTree.fromstring(xml_body)
                    for market in root.findall(".//market"):
                        for outcome in market.findall("outcome"):
                            grade_bet(market.get("id"), outcome.get("result"))

                elif msg_type == "alive":
                    pass  # heartbeat — feed is healthy


asyncio.run(main())

Support

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