При створенні інтерактивних ботів у Telegram ключовим інструментом взаємодії з користувачем виступають інлайн-кнопки (InlineKeyboardMarkup). На відміну від звичайних клавіатур (ReplyKeyboardMarkup), натискання на інлайн-кнопку генерує подію callback_query, яка надсилається на ваш Webhook без засмічення чату текстовими повідомленнями.
Однак при розробці логіки зміни екранів, фільтрів або вибору товарів розробники регулярно стикаються з жорсткими технічними обмеженнями Telegram API: суворо лімітованим розміром поля callback_data, підтримкою ідемпотентності запитів і необхідністю негайного підтвердження натискання кнопки.
Ліміт 64 байти в callback_data: чому не можна передавати JSON
Головне архітектурне обмеження параметра callback_data в об'єкті InlineKeyboardButton — його розмір не повинен перевищувати 64 байти. Важливо враховувати, що йдеться саме про байти, а не про символи. У кодуванні UTF-8 кириличні символи займають по 2 байти, а спеціальні символи та emoji — до 4 байт.
Спроба передати структурований JSON на зразок {\"action\":\"show_category\",\"id\":1042,\"page\":3} призведе до того, що Telegram API поверне помилку 400 Bad Request: BUTTON_DATA_INVALID. Нижче наведено приклад коректної функції для надсилання cURL-запитів до API з перевіркою помилок.
<?php
function sendTelegramApiRequest(string $method, array $params = []): array
{
$token = getenv('TELEGRAM_BOT_TOKEN');
if (!$token) {
throw new RuntimeException('TELEGRAM_BOT_TOKEN environment variable is not set.');
}
$url = "https://api.telegram.org/bot{$token}/{$method}";
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_CONNECTTIMEOUT => 5,
]);
$response = curl_exec($ch);
$curlError = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false) {
throw new RuntimeException("cURL error during Telegram API call: {$curlError}");
}
$decoded = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('Failed to parse JSON response from Telegram API.');
}
if ($httpCode !== 200 || !isset($decoded['ok']) || $decoded['ok'] !== true) {
$description = $decoded['description'] ?? 'Unknown error';
throw new RuntimeException("Telegram API error [HTTP {$httpCode}]: {$description}");
}
return $decoded['result'];
}
Архітектура коротких payload: префікси та генератор хешів
Для вкладання в ліміт 64 байти застосовують дві стратегії:
- Префіксна схема з двокрапкою або слешем: підходить для простих дій. Наприклад:
act:item:1042:3(деact— дія,item— сутність,1042— ID,3— сторінка). Це займає близько 16 байт. - Схема з тимчасовим сховищем у БД (Payload Registry): якщо контекст кнопки занадто великий (складні фільтри, довгі UUID, попередній склад замовлення), у
callback_dataпередають короткий унікальний ідентифікатор, а дані зберігають у PostgreSQL/MySQL або Redis.
Нижче наведено приклад створення короткого ключа запису для форми заявки з використанням криптографічно стійкого ідентифікатора lead_id.
<?php
function createLeadContext(PDO $pdo, int $userId, array $leadData): string
{
// Генерируем короткий уникальный ID длиною 14 символов (7 байт hex)
$leadId = bin2hex(random_bytes(7));
$stmt = $pdo->prepare('
INSERT INTO lead_contexts (lead_id, user_id, payload_data, created_at)
VALUES (:lead_id, :user_id, :payload_data, NOW())
');
$stmt->execute([
':lead_id' => $leadId,
':user_id' => $userId,
':payload_data' => json_encode($leadData, JSON_UNESCAPED_UNICODE),
]);
// Формируем callback_data, который занимает всего 18 байт: "confirm_lead:a1b2c3d4e5f6g7"
return "cnf_ld:{$leadId}";
}
Валідація Webhook, Secret Token та ідемпотентність
При обробці вхідних оновлень від Telegram необхідно суворо перевіряти заголовок X-Telegram-Bot-Api-Secret-Token, заданий при виклику setWebhook. Це захищає вашу кінцеву точку від підроблених HTTP-запитів.
Крім того, Telegram гарантує доставку оновлень за принципом «at least once» (принаймні один раз). Через мережеві збої один і той самий update_id може прийти повторно. Щоб не виконувати списання коштів або дублювання заявок двічі, оброблені update_id слід зберігати в базі даних і перевіряти перед запуском бізнес-логіки.
Обов'язковий виклик answerCallbackQuery та безшовний UX з editMessageText
При натисканні на інлайн-кнопку на клієнті користувача з'являється індикатор завантаження (годинничок, що крутиться на кнопці). Якщо сервер не відповість викликом методу answerCallbackQuery протягом 10 секунд, інтерфейс зависне, а Telegram на клієнті покаже помилку тайм-аута.
Правильний життєвий цикл обробки події callback_query виглядає наступним чином:
- Валідувати секретный токен і спарсити вхідний JSON.
- Перевірити
update_idна предмет повторної обробки (ідемпотентність). - Викликати
answerCallbackQueryдля зняття індикатора завантаження на кнопці (можна передатиtextтаshow_alert => trueдля спливаючого повідомлення). - Оновити поточне повідомлення через
editMessageTextабоeditMessageReplyMarkupдля відмальовування нового стану інтерфейсу.
<?php
// Входная точка обработки Webhook
$secretHeader = $_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? '';
$expectedSecret = getenv('TELEGRAM_WEBHOOK_SECRET');
if (!hash_equals($expectedSecret, $secretHeader)) {
http_response_code(403);
echo json_encode(['error' => 'Invalid secret token']);
exit;
}
$rawInput = file_get_contents('php://input');
$update = json_decode($rawInput, true);
if (!$update || !isset($update['update_id'])) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON update']);
exit;
}
$pdo = new PDO('mysql:host=127.0.0.1;dbname=bot_db;charset=utf8mb4', 'db_user', 'db_pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$updateId = (int)$update['update_id'];
// Проверка идемпотентности
$stmt = $pdo->prepare('INSERT IGNORE INTO processed_updates (update_id) VALUES (:update_id)');
$stmt->execute([':update_id' => $updateId]);
if ($stmt->rowCount() === 0) {
// Данный update_id уже был успешно обраборан ранее
http_response_code(200);
echo json_encode(['status' => 'already_processed']);
exit;
}
if (isset($update['callback_query'])) {
$callback = $update['callback_query'];
$callbackId = $callback['id'];
$callbackData = $callback['data'] ?? '';
$message = $callback['message'];
$chatId = $message['chat']['id'];
$messageId = $message['message_id'];
// 1. Снимаем лоадер с кнопки
sendTelegramApiRequest('answerCallbackQuery', [
'callback_query_id' => $callbackId,
'text' => 'Загрузка...',
'show_alert' => false,
]);
// 2. Разбираем сжатый payload
$parts = explode(':', $callbackData);
$action = $parts[0] ?? '';
if ($action === 'cnf_ld') {
$leadId = $parts[1] ?? '';
// Получаем контекст лида из БД
$stmtContext = $pdo->prepare('SELECT payload_data FROM lead_contexts WHERE lead_id = :lead_id');
$stmtContext->execute([':lead_id' => $leadId]);
$contextRow = $stmtContext->fetch();
if ($contextRow) {
$leadData = json_decode($contextRow['payload_data'], true);
// 3. Обновляем текст сообщения и менять клавиатуру
sendTelegramApiRequest('editMessageText', [
'chat_id' => $chatId,
'message_id' => $messageId,
'text' => "Заявка #{$leadId} успешно подтверждена!
Услуга: " . htmlspecialchars($leadData['service'] ?? 'Неуказана'),
'parse_mode' => 'HTML',
'reply_markup' => [
'inline_keyboard' => [
[
['text' => '« Назад в меню', 'callback_data' => 'main_menu']
]
]
]
]);
}
}
}
http_response_code(200);
echo json_encode(['status' => 'ok']);
Типові помилки при роботі з Inline-клавіатурами
- Ігнорування помилок editMessageText: якщо спробувати відредагувати повідомлення, передавши точно такий самий текст і
reply_markup, Telegram API поверне помилку400 Bad Request: message is not modified. Логуйте відгуки API та перехоплюйте винятки. - Передача чутливих даних у callback_data: пам'ятайте, що
callback_dataне шифрується всередині інфраструктури Telegram і може залишитися в логах клієнта або сервера. Не передавайте там токени авторизації або персональні дані. - Відсутність обробки застарілих кнопок: користувач може натиснути на інлайн-кнопку в повідомленні тижневої давнини. Система повинна коректно обробляти ситуації, коли тимчасовий контекст у БД вже очищено за TTL.
Якщо вам потрібна професійна інтеграція складних Telegram-бот ів та Mini Apps з надійною архітектурою, замовте розробку у фахівців на BotCreator.
"