TL;DR: TON-based poker apps on Telegram offer real-time game data that developers can use to build analytics tools, HUDs, or automated bankroll trackers. This guide walks through the API patterns, stake structures, and practical implementation strategies I've discovered while building tools for these platforms.
Understanding the TON Poker Ecosystem
Before writing a single line of code, you need to understand how these apps expose game data. Unlike traditional online poker rooms, TON Telegram poker apps use a hybrid architecture:
- Game Logic Layer: Handled server-side on TON blockchain smart contracts
- UI Layer: Telegram Mini Apps (WebApp API)
- Data Layer: Real-time updates via WebSocket connections
When I started reverse-engineering these apps in early 2025, I found three consistent patterns:
| Component | Technology | Developer Access |
|---|---|---|
| Table State | WebSocket events | Read-only (public tables) |
| Player Actions | Telegram Bot API | Restricted |
| Hand History | JSON payloads | Downloadable after session |
| Balance Updates | TON Connect | With user permission |
The Stake Structure You'll Encounter
Here's the practical reality: different apps use different stake formats. Through testing across multiple platforms including ChainPoker, I've mapped the standard denomination system:
const stakeLevels = {
micro: {
blinds: [0.01, 0.02],
minBuyIn: 1,
maxBuyIn: 5,
activeHours: "always" // 24/7 liquidity
},
low: {
blinds: [0.10, 0.25],
minBuyIn: 10,
maxBuyIn: 40,
activeHours: "peak+4" // 4 hours around UTC evening
},
mid: {
blinds: [0.50, 1.00],
minBuyIn: 50,
maxBuyIn: 200,
activeHours: "peak" // UTC 18:00-22:00
},
high: {
blinds: [2.00, 5.00],
minBuyIn: 200,
maxBuyIn: 1000,
activeHours: "variable" // Depends on player pool
}
};
Key insight I learned the hard way: Table liquidity follows a power law distribution. Micro stakes will have 10x more tables than low stakes at any given time. If you're building a bot that needs consistent matchmaking, target the micro pool.
Building a Table Scanner
Here's a minimal WebSocket client that monitors available tables:
const WebSocket = require('ws');
class TONPokerScanner {
constructor(appEndpoint) {
this.ws = new WebSocket(appEndpoint);
this.tables = new Map();
}
connect() {
this.ws.on('message', (data) => {
const payload = JSON.parse(data.toString());
if (payload.type === 'table_update') {
this.processTable(payload.table);
}
if (payload.type === 'table_close') {
this.tables.delete(payload.tableId);
}
});
// Subscribe to all stake levels
this.ws.send(JSON.stringify({
action: 'subscribe',
channels: ['tables:*']
}));
}
processTable(table) {
const key = `${table.gameType}:${table.blinds}`;
if (!this.tables.has(key)) {
this.tables.set(key, {
count: 0,
avgPlayers: 0,
lastSeen: Date.now()
});
}
const entry = this.tables.get(key);
entry.count++;
entry.avgPlayers = (entry.avgPlayers + table.players) / 2;
}
getStakeReport(stakeLevel) {
const report = {};
for (const [key, data] of this.tables) {
if (key.includes(stakeLevel)) {
report[key] = data;
}
}
return report;
}
}
// Usage
const scanner = new TONPokerScanner('wss://api.tonpoker.app/tables');
scanner.connect();
Parsing Hand Histories
Once you've found active tables, you'll want to analyze hand data. Most TON poker apps expose hand histories in a standard JSON format. Here's how I parse them:
class HandHistoryParser {
constructor(rawData) {
this.hands = this.parseBatch(rawData);
}
parseBatch(data) {
return data.hands.map(hand => ({
id: hand.id,
gameType: hand.game_type, // 'holdem', 'omaha', 'short_deck'
blinds: hand.blinds,
players: hand.players.map(p => ({
id: p.id,
position: p.seat,
stack: p.stack_at_start,
vpip: this.calculateVPIP(p.actions)
})),
actions: this.parseActions(hand.actions),
showdown: hand.showdown || null
}));
}
calculateVPIP(actions) {
if (!actions || actions.length === 0) return 0;
const preflopActions = actions.filter(a => a.street === 'preflop');
const voluntaryCallsOrRaises = preflopActions.filter(a =>
(a.type === 'call' && a.facing_bet > 0) ||
a.type === 'raise'
);
return (voluntaryCallsOrRaises.length / preflopActions.length) * 100;
}
parseActions(actions) {
return actions.map(action => ({
street: action.street,
player: action.player_id,
type: action.action_type, // 'fold', 'check', 'call', 'raise', 'all_in'
amount: action.amount || 0,
timestamp: action.timestamp
}));
}
}
Practical Analytics Dashboard
Here's a real dashboard component I built that tracks game selection efficiency:
class GameSelectionAnalyzer {
constructor(sessionHistory) {
this.sessions = sessionHistory;
}
calculateWinRateByStake() {
const stats = {};
for (const session of this.sessions) {
const key = `${session.gameType}:${session.blinds}`;
if (!stats[key]) {
stats[key] = {
sessions: 0,
totalProfit: 0,
totalHours: 0,
bbPer100: 0
};
}
const entry = stats[key];
entry.sessions++;
entry.totalProfit += session.profit;
entry.totalHours += session.duration_hours;
// Calculate big blinds per 100 hands
const handsPlayed = session.hands_played || 100;
const bigBlind = session.blinds[1]; // Big blind is second element
entry.bbPer100 = (session.profit / bigBlind) * (100 / handsPlayed);
}
return stats;
}
findOptimalStakes() {
const stats = this.calculateWinRateByStake();
return Object.entries(stats)
.filter(([_, data]) => data.sessions >= 10) // Minimum sample
.sort((a, b) => b[1].bbPer100 - a[1].bbPer100)
.map(([key, data]) => ({
game: key,
winRate: data.bbPer100.toFixed(2),
confidence: Math.min(data.sessions / 50, 1) // Scale to 0-1
}));
}
}
Common Pitfalls I've Encountered
WebSocket rate limiting - Most apps limit to 10 connections per IP. I solved this by rotating through a proxy pool.
Stake format inconsistencies - Some apps use TON instead of USD. Always normalize to USD using current TON price before analysis.
Tournament vs Cash differences - Spin & Go tournaments use a completely different data structure. My parser needed separate handlers for each game type.
Empty table detection - Some apps show "ghost tables" that never fill. Add a 5-minute timeout filter.
Putting It All Together
Here's the complete flow I use for nightly analysis:
# 1. Scan for active tables
node scanner.js --output tables.json
# 2. Download hand histories from profitable tables
python fetch_hands.py --tables tables.json --output hands/
# 3. Calculate stats and generate report
node analyze.js --hands hands/ --report report.md
# 4. Send digest via Telegram
python notify.py --report report.md
Pro tip: Start with micro stakes on platforms like ChainPoker where the player pool is largest. The lower variance will give you cleaner data for building and testing your tools.
Resources for Further Development
- TON Blockchain API docs (for smart contract interaction)
- Telegram Mini Apps WebSocket documentation
- Open-source hand history converters (PokerStars format works as reference)
The ecosystem is growing fast. When I started building these tools, only 3 apps had public APIs. Now most major TON poker apps including ChainPoker provide developer endpoints. The key is starting simple - get a table scanner working first, then add analytics. Your bot is only as good as the data pipeline feeding it.
If you're tinkering with the same setup, the ChainPoker Telegram bot is here: https://go.chainpk.top/r/geo_auto_202606_t_20260519_010848_4879
Top comments (0)