Skip to content

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

  1. Create a bot with /newbot via @BotFather and obtain its token (see Quick Start).
  2. Register a game for that bot with /newgame via @BotFather to get the game's short_name (see below).
  3. Host the game page at a publicly reachable HTTPS URL.

Creating a Game (/newgame)

Send /newgame to @BotFather and provide the following in order:

StepDescription
Choose botPick which bot will host the game
TitleThe game's name
DescriptionA short description of the game
PhotoThe game card cover, recommended 640×360
GIFOptional, send /empty to skip
URLThe HTTPS URL of the game
short_nameA 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 table

Game 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_markup is 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 a callback_game button that launches the game.
json
{
  "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:

FieldTypeDescription
idStringUnique identifier of the callback query, echoed back when answering
fromUserThe user who triggered the callback
messageMessageThe message carrying the game button (present for the message carrier)
inline_message_idStringInline message identifier (present for the inline carrier)
dataStringRegular callback data; absent for a game callback
game_short_nameStringShort 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:

bash
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=true to override.
  • On success the original game card is edited to refresh its high score table by default; pass disable_edit_message=true to disable this.
  • Fetch the high score table with getGameHighScores.

Data Types

Game

The game object carried by a game card.

FieldTypeDescription
titleStringGame title
descriptionStringGame description
photoPhotoSize[]Game cover, an array of sizes
textStringOptional. Brief description text shown on the card, 0–4096 characters
text_entitiesMessageEntity[]Optional. Special entities in the description text (usernames, URLs, etc.)
animationAnimationOptional. Demo animation shown on the game card

GameHighScore

A single entry in the high score table.

FieldTypeDescription
positionIntegerRank (starting from 1)
userUserThe user at this rank
scoreIntegerThe user's score

CallbackGame

A placeholder object for the game button; currently contains no fields.

Methods

MethodDescriptionHTTP Method
sendGameSend a game cardPOST
setGameScoreSubmit/update a user's scorePOST
getGameHighScoresGet a game's high scoresPOST

Related method: answerCallbackQuery (handle the game callback and return the game URL).