Games
Games let your bot post a special "game card" into a chat. When a user taps the button on the card, the client opens an HTML5 game in an in-app WebView. When the game ends it reports the score back through the Bot API, and the high score table is shown right on the card.
Games are built on SafeW's HTML5 Games protocol and are separate from Mini Apps: the game runs in a regular web page and does not rely on Mini App WebApp features such as fullscreen, safe areas, or the main button.
Prerequisites
- Create a bot with
/newbotvia @BotFather and obtain its token (see Quick Start). - Register a game for that bot with
/newgamevia @BotFather to get the game'sshort_name(see below). - Host the game page at a publicly reachable HTTPS URL.
Creating a Game (/newgame)
Send /newgame to @BotFather and provide the following in order:
| Step | Description |
|---|---|
| Choose bot | Pick which bot will host the game |
| Title | The game's name |
| Description | A short description of the game |
| Photo | The game card cover, recommended 640×360 |
| GIF | Optional, send /empty to skip |
| URL | The HTTPS URL of the game |
| short_name | A unique identifier for the game, 3–30 characters, a-z A-Z 0-9 _ only |
Once created, the short_name can be used as the game_short_name for sendGame. The short_name must be unique within a bot.
Workflow
1. Send the game card
Bot ── sendGame{chat_id, game_short_name} ──▶ a game card appears in the chat
2. User opens the game
The user taps the game button on the card
→ The bot receives a callback_query (with game_short_name, no data)
→ The bot replies with answerCallbackQuery{url} returning the game URL
→ The client opens that URL in a WebView
3. Report the score
In game ── setGameScore{user_id, score, …} ──▶ the server records the score
→ By default the original card is edited to refresh the high score table
4. View the high scores
Bot/client ── getGameHighScores{user_id, …} ──▶ returns the high score tableGame Button (callback_game)
The button on a game card is a special inline keyboard button: it carries a callback_game field, and tapping it triggers a game callback (instead of a regular callback_data callback).
- When calling sendGame, if no
reply_markupis supplied the server automatically appends a "Play + game title" button. - If you supply a custom
reply_markup, the first button of the first row must be acallback_gamebutton that launches the game.
{
"inline_keyboard": [
[{ "text": "▶️ Play", "callback_game": {} }],
[{ "text": "How to play", "url": "https://example.com/how-to-play" }]
]
}callback_game is currently a placeholder object and requires no fields.
Opening the Game (handling the game callback)
When a user taps the game button, the bot receives a callback_query via getUpdates or a webhook. A game callback is identified by a non-empty game_short_name and the absence of a data field, which is how you distinguish it from a regular callback_data callback.
Key fields of CallbackQuery:
| Field | Type | Description |
|---|---|---|
| id | String | Unique identifier of the callback query, echoed back when answering |
| from | User | The user who triggered the callback |
| message | Message | The message carrying the game button (present for the message carrier) |
| inline_message_id | String | Inline message identifier (present for the inline carrier) |
| data | String | Regular callback data; absent for a game callback |
| game_short_name | String | Short name of the game that triggered the callback; used to identify a game callback |
On receiving it, the bot should call answerCallbackQuery and return the game URL in url:
curl -X POST "https://api.safew.bot/<token>/answerCallbackQuery" \
-H "Content-Type: application/json" \
-d '{
"callback_query_id": "<callback_query_id>",
"url": "https://example.com/game?session=<signed_token>"
}'It is recommended to attach a one-time, signed, time-limited session parameter to url (identifying the user_id, the chat/message, and an expiry) so the game front end can locate the instance when calling setGameScore and to prevent forged scores.
The platform may also serve a signed game URL directly from the server (without a bot round-trip). The
answerCallbackQuery{url}flow is therefore the standard, general-purpose option rather than the only one.
Submitting and Showing Scores
- After the game ends, submit the score with setGameScore. By default the new score must be strictly greater than the user's current score, otherwise it is rejected; use
force=trueto override. - On success the original game card is edited to refresh its high score table by default; pass
disable_edit_message=trueto disable this. - Fetch the high score table with getGameHighScores.
Data Types
Game
The game object carried by a game card.
| Field | Type | Description |
|---|---|---|
| title | String | Game title |
| description | String | Game description |
| photo | PhotoSize[] | Game cover, an array of sizes |
| text | String | Optional. Brief description text shown on the card, 0–4096 characters |
| text_entities | MessageEntity[] | Optional. Special entities in the description text (usernames, URLs, etc.) |
| animation | Animation | Optional. Demo animation shown on the game card |
GameHighScore
A single entry in the high score table.
| Field | Type | Description |
|---|---|---|
| position | Integer | Rank (starting from 1) |
| user | User | The user at this rank |
| score | Integer | The user's score |
CallbackGame
A placeholder object for the game button; currently contains no fields.
Methods
| Method | Description | HTTP Method |
|---|---|---|
| sendGame | Send a game card | POST |
| setGameScore | Submit/update a user's score | POST |
| getGameHighScores | Get a game's high scores | POST |
Related method: answerCallbackQuery (handle the game callback and return the game URL).
