Interactive inline keyboards (InlineKeyboardMarkup) are the primary tool for user interaction with a bot without sending text commands. However, when implementing them, developers often encounter hidden pitfalls Telegram Bot API: a strict 64-byte size limit on callback_data, stuck loading indicators on buttons, and race conditions during repeated webhooks.
In this article, we will examine in detail how to properly design a concise payload, implement clients for making API calls via cURL, handle callback_query events, and seamlessly update the interface using editMessageText.
The 64-Byte Limit and Short Payload Design
The callback_data field in the InlineKeyboardButton object has a fundamental limitation: its length cannot exceed 64 bytes in UTF-8 encoding. If you pass a JSON payload that is too large or a long string identifier, the Telegram Bot API will return an HTTP 400 error with the status Bad Request: BUTTON_DATA_INVALID.
Attempting to store order state, entity names, or complex filters directly inside the button is the main mistake made by beginner developers. The callback_data must be as compact as possible, containing only a short action prefix and a unique record identifier.
Data Compression Strategies:
- Short command prefixes: Instead of
action=confirm_order, usecnforact:c. - Generated hashes or binary keys: For temporary states, use short database keys (for example,
bin2hex(random_bytes(7))) or a Redis store. - Minimal-length delimiters: Use
:or|characters to separate arguments instead of complex formats.
Below is a class for encoding and decoding commands passed to buttons with hard byte limit validation:
<?php
declare(strict_types=1);
class CallbackPayload
{
private const MAX_BYTES = 64;
private const DELIMITER = ':';
public static function pack(string $action, string|int $id): string
{
$payload = $action . self::DELIMITER . $id;
if (strlen($payload) > self::MAX_BYTES) {
throw new InvalidArgumentException(
sprintf('Payload exceeds %d bytes limit: "%s"', self::MAX_BYTES, $payload)
);
}
return $payload;
}
public static function unpack(string $payload): array
{
$parts = explode(self::DELIMITER, $payload, 2);
return [
'action' => $parts[0] ?? '',
'id' => $parts[1] ?? '',
];
}
}
A Reliable cURL HTTP Client for Telegram Bot API Calls
To execute requests to the Telegram API, you should not use the file_get_contents function, as it does not allow flexible management of timeouts, headers, and network-level error handling. Using the cURL library guarantees control over request execution.
When sending answerCallbackQuery and editMessageText commands, it is essential to check three levels of errors: the cURL execution status itself, the validity of the received JSON, and the ok: true status in Telegram's internal response.
<?php
declare(strict_types=1);
function callTelegramApi(string $method, array $params = []): array
{
$token = getenv('TELEGRAM_BOT_TOKEN');
if (!$token) {
throw new RuntimeException('Environment variable TELEGRAM_BOT_TOKEN is not set.');
}
$url = sprintf('https://api.telegram.org/bot%s/%s', $token, $method);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($params, JSON_UNESCAPED_UNICODE),
CURLOPT_TIMEOUT => 5,
CURLOPT_CONNECTTIMEOUT => 3,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($response === false) {
throw new RuntimeException('cURL error: ' . $error);
}
$decoded = json_decode((string)$response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON parse error: ' . json_last_error_msg());
}
if ($httpCode !== 200 || empty($decoded['ok'])) {
$description = $decoded['description'] ?? 'Unknown API Error';
throw new RuntimeException(sprintf('Telegram API Error [%d]: %s', $httpCode, $description));
}
return $decoded['result'];
}
Why answerCallbackQuery Is Mandatory
When a user taps an inline button, the Telegram client enters a waiting state and displays a spinning loader indicator on the button itself. If the bot server does not respond by calling the answerCallbackQuery method, the animation will continue for about 60 seconds, creating the impression that the bot has frozen.
You must call answerCallbackQuery as quickly as possible by passing the callback_query_id. The method accepts optional parameters:
text: text of the pop-up notification (up to 200 characters).show_alert: iffalse(default), a short toast notification appears at the top of the screen. Iftrue, a modal window with an "OK" button will open.
Editing Messages via editMessageText
After receiving a button click, it is often necessary to modify the original message: update the статус заказа, change the text, or completely remove/replace the keyboard. The editMessageText method is used for this purpose.
To identify the message to be updated, the chat_id and stream parameters extracted from the callback_query['message'] object are passed to the method.
Important: If you send an absolutely identical text andreply_markupstructure toeditMessageText, Telegram will return a400 Bad Request: message is not modifiederror. This error should be caught and ignored.
Full Webhook Handler Implementation
Let's combine all the steps into a complete webhook handler script. It implements secret verification for X-Telegram-Bot-Api-Secret-Token, deduplication of incoming events by update_id (to prevent re-processing during network failures), calling answerCallbackQuery, and subsequently replacing the text via editMessageText.
<?php
declare(strict_types=1);
header('Content-Type: application/json');
// 1. Проверка Secret Token от Telegram
$secretToken = getenv('TELEGRAM_SECRET_TOKEN') ?: 'my-super-secret-token';
$incomingSecret = $_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? '';
if (!hash_equals($secretToken, $incomingSecret)) {
http_response_code(403);
echo json_encode(['ok' => false, 'error' => 'Forbidden']);
exit;
}
// 2. Чтение входящего Update
$rawContent = file_get_contents('php://input');
$update = json_decode((string)$rawContent, true);
if (json_last_error() !== JSON_ERROR_NONE || !isset($update['update_id'])) {
http_response_code(400);
echo json_encode(['ok' => false, 'error' => 'Invalid JSON']);
exit;
}
// 3. Проверка идемпотентности по update_id (SQLite для примера)
$pdo = new PDO('sqlite:' . __DIR__ . '/updates.sqlite');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec('CREATE TABLE IF NOT EXISTS processed_updates (update_id INTEGER PRIMARY KEY)');
$stmt = $pdo->prepare('INSERT OR IGNORE INTO processed_updates (update_id) VALUES (?)');
$stmt->execute([$update['update_id']]);
if ($stmt->rowCount() === 0) {
// Данный update_id уже был успешно обработан ранее
http_response_code(200);
echo json_encode(['ok' => true, 'status' => 'duplicate_skipped']);
exit;
}
// 4. Обработка callback_query
if (isset($update['callback_query'])) {
$cq = $update['callback_query'];
$cqId = $cq['id'];
$data = $cq['data'] ?? '';
$chatId = $cq['message']['chat']['id'] ?? null;
$messageId = $cq['message']['message_id'] ?? null;
$parsed = CallbackPayload::unpack($data);
$action = $parsed['action'];
$leadId = $parsed['id'];
try {
// Обязательно гасим лоадер на кнопке
callTelegramApi('answerCallbackQuery', [
'callback_query_id' => $cqId,
'text' => 'Выбор принят',
'show_alert' => false,
]);
if ($action === 'cnf' && $chatId && $messageId) {
// Менявм текст сообщения и удаляем клавиатуру
callTelegramApi('editMessageText', [
'chat_id' => $chatId,
'message_id' => $messageId,
'text' => sprintf('Заявка <b>#%s</b> успешно подтверждена!', htmlspecialchars($leadId)),
'parse_mode' => 'HTML',
'reply_markup' => [
'inline_keyboard' => []
],
]);
}
} catch (Throwable $e) {
error_log('Callback processing error: ' . $e->getMessage());
}
}
http_response_code(200);
echo json_encode(['ok' => true]);
Proper payload design and adherence to the Bot API calling standards allow you to build a fast and fault-tolerant inline keyboard interface in pure PHP without delays or erroneous states.
If you need the development of reliable Telegram bots and web applications integrated into business processes, contact the specialists at BotCreator.