Telegram Bot API: готовые сниппеты кода
Девять сниппетов, покрывающих полный цикл запроса Telegram-бота: проверка вебхука, определение пользователя, разбор deep-link, экранирование MarkdownV2, раскладка inline-клавиатуры и ответ на callback. JavaScript и PHP.
Почти любой Telegram-бот повторяет один и тот же цикл: на вебхук приходит апдейт, бот выясняет, кто его прислал, отвечает форматированным текстом с клавиатурой и обрабатывает нажатие кнопки. Справочник Bot API описывает каждый метод по отдельности, поэтому то, что реально доставляет проблемы — экранирование MarkdownV2, форма объекта update, дедлайн в пару секунд на ответ callback — разнесено по десятку страниц. Эти девять сниппетов и есть тот самый цикл, по порядку.
Проверяйте вебхук до того, как поверите данным
Эндпоинт вебхука — публичный URL. Кто угодно, найдя его, может отправить POST с JSON, неотличимым от настоящего апдейта, и бот без проверки честно примет его за реальный. Ответ Telegram — параметр secret_token, который передаётся в setWebhook и возвращается на каждом запросе в заголовке X-Telegram-Bot-Api-Secret-Token. Сравнивать его нужно за постоянное время и отклонять несовпадение до разбора хотя бы одного поля.
Пользователь лежит не там, где ожидается
Единого update.user не существует. В зависимости от того, что пришло — сообщение, отредактированное сообщение, callback-запрос или inline-запрос, — отправитель находится под разным ключом. Код update.message.from.id работает ровно до первого нажатия кнопки: там message не определён, и бот падает. Приведение апдейта к одному объекту пользователя на входе снимает целый класс плавающих ошибок.
Deep-link — единственное состояние до начала диалога
t.me/yourbot?start=<payload> — это единственное, что Telegram передаст боту до начала разговора, поэтому именно так атрибутируют регистрацию, возобновляют оплату или связывают чат с записью в своей базе. Значение приходит текстом после /start, ограничено 64 символами и допускает только URL-безопасный алфавит — то есть туда кладут токен или идентификатор, а не сериализованный объект.
Экранирование MarkdownV2 строже, чем кажется
MarkdownV2 требует экранировать восемнадцать символов, включая те, что постоянно встречаются в обычном тексте: ., -, !, (, ), +. Пропущенный символ приводит не к отправке без форматирования, а к отказу API с ошибкой 400 — то есть одна неэкранированная точка в пользовательском тексте роняет весь ответ. Внутри блоков кода и в адресах ссылок правила экранирования другие, и самописные экранировщики обычно ломаются именно там.
Клавиатуры и callback
Inline-клавиатура — это массив рядов, и Telegram не раскладывает её за вас: ряд из шести кнопок так и останется рядом из шести нечитаемых кнопок на телефоне. Отвечать на callback тоже обязательно: пока не вызван answerCallbackQuery, клиент показывает на кнопке крутилку, а сам вызов должен уложиться в несколько секунд после нажатия.
Каждый сниппет ниже самодостаточен — берите нужный. Вместе они складываются в рабочий скелет цикла обработки запроса.
Разбор deep-link параметров Telegram /start
Извлекает источник трафика, UTM-метки или ID реферера из payload, который Telegram передаёт после /start — стандартный способ отслеживать, откуда пришли пользователи бота.
Как это работает: диплинки Telegram выглядят как t.me/YourBot?start=PAYLOAD, и бот получает это как /start PAYLOAD; простое соглашение о префиксах (ref_, utm_, referral_) в payload позволяет закодировать данные атрибуции без дополнительных вызовов API.
function parseStartParams(startCommand) {
const [, param] = startCommand.split(' ')
if (!param) return { source: 'telegram' }
// /start ref_telegram_ad
if (param.startsWith('ref_')) {
return { source: param.replace('ref_', '') }
}
// /start utm_source-google_medium-cpc_campaign-spring
if (param.startsWith('utm_')) {
const utm = {}
param.replace('utm_', '').split('_').forEach((part) => {
const [key, value] = part.split('-')
if (key && value) utm[`utm_${key}`] = decodeURIComponent(value)
})
return { source: utm.utm_source || 'unknown', medium: utm.utm_medium, campaign: utm.utm_campaign, utm }
}
// /start referral_12345
if (param.startsWith('referral_')) {
const referrerId = parseInt(param.replace('referral_', ''), 10)
if (!Number.isNaN(referrerId)) return { source: 'referral', referrerId }
}
return { source: param }
}
// Example usage
parseStartParams('/start ref_instagram_bio')
// { source: 'instagram_bio' }
parseStartParams('/start referral_12345')
// { source: 'referral', referrerId: 12345 }
Ответ на callback_query Telegram
Отвечает на нажатие инлайн-кнопки (answerCallbackQuery) без SDK — убирает лоадер с кнопки, опционально показывает уведомление или alert пользователю.
Как это работает: Telegram требует ответить на каждый callback_query в течение 10 секунд, иначе у пользователя будет крутиться лоадер на кнопке. answerCallbackQuery закрывает его. Опциональный text показывает всплывающее уведомление (toast или alert).
async function answerCallbackQuery(token, callbackQueryId, options = {}) {
const url = `https://api.telegram.org/bot${token}/answerCallbackQuery`
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
callback_query_id: callbackQueryId,
...options,
}),
})
if (!res.ok) throw new Error(`Telegram API error: ${res.status}`)
return res.json()
}
// Example usage — тихий ответ (только убрать лоадер)
await answerCallbackQuery(token, query.id)
// С уведомлением (toast внизу экрана, до 200 символов)
await answerCallbackQuery(token, query.id, {
text: 'Заказ подтверждён ✅',
show_alert: false,
})
// Как alert (модальный диалог)
await answerCallbackQuery(token, query.id, {
text: 'Действие недоступно. Попробуйте позже.',
show_alert: true,
})
Получить ID и данные пользователя из Telegram-апдейта
Извлекает ID, имя и username пользователя из любого типа входящего Telegram-апдейта — message, callback_query, edited_message, inline_query и других.
Как это работает: Telegram может прислать апдейт разных типов, и поле from (данные пользователя) находится в разных местах. Функция пробегает по всем возможным путям и возвращает нормализованный объект или null, если апдейт не содержит данных пользователя (например, канальный пост).
function getUserFromUpdate(update) {
const from =
update.message?.from ??
update.callback_query?.from ??
update.edited_message?.from ??
update.inline_query?.from ??
update.chosen_inline_result?.from ??
update.pre_checkout_query?.from ??
update.my_chat_member?.from ??
null
if (!from) return null
return {
id: from.id,
username: from.username ?? null,
firstName: from.first_name,
lastName: from.last_name ?? null,
fullName: [from.first_name, from.last_name].filter(Boolean).join(' '),
isBot: from.is_bot ?? false,
languageCode: from.language_code ?? null,
isPremium: from.is_premium ?? false,
}
}
function getChatIdFromUpdate(update) {
return (
update.message?.chat?.id ??
update.callback_query?.message?.chat?.id ??
update.edited_message?.chat?.id ??
null
)
}
// Example usage
const user = getUserFromUpdate(update)
// { id: 123456789, username: 'alice', fullName: 'Alice Smith', ... }
const chatId = getChatIdFromUpdate(update)
// 123456789
Ссылка на пользователя Telegram и mention
Генерирует кликабельную ссылку на пользователя Telegram и mention-разметку для MarkdownV2 — через username если есть, иначе через tg://user?id= deep link.
Как это работает: если у пользователя есть username — используем t.me/username. Если нет — deep link tg://user?id=, который открывает профиль напрямую. Для mention в MarkdownV2 оборачиваем имя в [text](tg://user?id=...).
/**
* Публичная ссылка на профиль (открывается в браузере/приложении)
*/
function telegramProfileLink(user) {
return user.username
? `https://t.me/${user.username}`
: `tg://user?id=${user.id}`
}
/**
* Mention в MarkdownV2 — кликабельное имя в тексте сообщения
*/
function telegramMentionMd(user) {
const name = [user.first_name, user.last_name]
.filter(Boolean)
.join(' ')
.replace(/[_*[\]()~`>#+\-=|{}.!]/g, '\\$&') // escape MarkdownV2
return `[${name}](tg://user?id=${user.id})`
}
/**
* Mention в HTML-разметке Telegram
*/
function telegramMentionHtml(user) {
const name = [user.first_name, user.last_name].filter(Boolean).join(' ')
return `<a href="tg://user?id=${user.id}">${name}</a>`
}
// Example usage
const user = { id: 123456789, username: 'alice', first_name: 'Alice', last_name: 'Smith' }
telegramProfileLink(user) // 'https://t.me/alice'
telegramMentionMd(user) // '[Alice Smith](tg://user?id=123456789)'
telegramMentionHtml(user) // '<a href="tg://user?id=123456789">Alice Smith</a>'
Экранирование текста для Telegram MarkdownV2
Bot API Telegram отклоняет сообщения с неэкранированными спецсимволами MarkdownV2 — эта функция безопасно экранирует их все.
Как это работает: экранируется каждый символ, который парсер MarkdownV2 в Telegram считает специальным. Это рецепт "экранировать всё" — безопасен для произвольного пользовательского текста, но учти, что он также заэкранирует markdown-разметку, которую ты написал сам (вроде *bold*), поэтому применяй его к динамическим значениям, которые вставляешь в шаблон сообщения, а не к разметке самого шаблона.
<?php
function escapeMarkdownV2(string $text): string
{
$reserved = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'];
return str_replace($reserved, array_map(fn ($c) => '\\' . $c, $reserved), $text);
}
// Example usage
$message = escapeMarkdownV2('Price: $10.99 (was $12.99!)');
// "Price: \$10\.99 \(was \$12\.99\!\)"
$bot->sendMessage([
'chat_id' => $chatId,
'text' => $message,
'parse_mode' => 'MarkdownV2',
]);
Получить ID пользователя Telegram через PHP-вебхук
Читает входящий Telegram-апдейт из php://input и возвращает нормализованный объект пользователя — id, username, имя и код языка. Работает с message, callback_query и другими типами.
Как это работает: Telegram присылает POST-запрос с JSON в теле. PHP читает его через php://input, парсит и ищет поле from в разных местах апдейта в зависимости от его типа.
<?php
function getTelegramUpdate(): ?array
{
$body = file_get_contents('php://input');
return $body ? json_decode($body, true) : null;
}
function getUserFromUpdate(array $update): ?array
{
$from = $update['message']['from']
?? $update['callback_query']['from']
?? $update['edited_message']['from']
?? $update['inline_query']['from']
?? $update['my_chat_member']['from']
?? null;
if (!$from) return null;
$firstName = $from['first_name'] ?? '';
$lastName = $from['last_name'] ?? '';
return [
'id' => (int) $from['id'],
'username' => $from['username'] ?? null,
'first_name' => $firstName,
'last_name' => $lastName,
'full_name' => trim("$firstName $lastName"),
'is_bot' => (bool) ($from['is_bot'] ?? false),
'language_code' => $from['language_code'] ?? null,
'is_premium' => (bool) ($from['is_premium'] ?? false),
];
}
function getChatIdFromUpdate(array $update): ?int
{
$chatId = $update['message']['chat']['id']
?? $update['callback_query']['message']['chat']['id']
?? $update['edited_message']['chat']['id']
?? null;
return $chatId !== null ? (int) $chatId : null;
}
// Example usage
$update = getTelegramUpdate();
if (!$update) exit;
$user = getUserFromUpdate($update);
$chatId = getChatIdFromUpdate($update);
// $user['id'] => 123456789
// $user['username'] => 'alice'
// $chatId => 123456789
Отправка сообщения через Telegram Bot API (PHP)
Отправляет сообщение через Telegram Bot API без SDK — только встроенный curl. Поддерживает MarkdownV2, ReplyMarkup, reply_to_message_id и любые другие параметры sendMessage.
Как это работает: функция формирует POST-запрос к api.telegram.org/bot{token}/sendMessage с JSON-телом. Все дополнительные параметры (parse_mode, reply_markup и т.д.) передаются через $options и мержатся с обязательными полями.
<?php
function telegramSendMessage(
string $token,
int|string $chatId,
string $text,
array $options = []
): array {
$url = "https://api.telegram.org/bot{$token}/sendMessage";
$payload = array_merge(['chat_id' => $chatId, 'text' => $text], $options);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
if ($error) throw new \RuntimeException("cURL error: $error");
return json_decode($response, true);
}
// Example usage — простое сообщение
telegramSendMessage($_ENV['BOT_TOKEN'], $chatId, 'Привет!');
// С Markdown и инлайн-кнопкой
telegramSendMessage($_ENV['BOT_TOKEN'], $chatId, 'Выберите действие:', [
'parse_mode' => 'MarkdownV2',
'reply_markup' => json_encode([
'inline_keyboard' => [[
['text' => '✅ Подтвердить', 'callback_data' => 'confirm'],
['text' => '❌ Отмена', 'callback_data' => 'cancel'],
]],
]),
]);
Верификация Telegram Webhook Secret
Проверяет заголовок X-Telegram-Bot-Api-Secret-Token входящего вебхук-запроса — защищает эндпоинт от посторонних запросов не из Telegram.
Как это работает: при регистрации вебхука (setWebhook) можно передать secret_token. Telegram будет добавлять его в заголовок X-Telegram-Bot-Api-Secret-Token каждого запроса. Сравниваем через hash_equals, чтобы избежать timing attack.
<?php
function verifyTelegramWebhookSecret(string $expectedSecret): bool
{
$header = $_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? '';
if ($header === '') return false;
return hash_equals($expectedSecret, $header);
}
// Example usage — первая строчка вебхук-обработчика
if (!verifyTelegramWebhookSecret($_ENV['TELEGRAM_WEBHOOK_SECRET'])) {
http_response_code(403);
exit;
}
$update = json_decode(file_get_contents('php://input'), true);
// ... обработка апдейта
Зарегистрировать вебхук с секретом:
<?php
function registerWebhook(string $token, string $url, string $secret): array
{
$endpoint = "https://api.telegram.org/bot{$token}/setWebhook";
$payload = ['url' => $url, 'secret_token' => $secret, 'allowed_updates' => ['message', 'callback_query']];
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
]);
$res = curl_exec($ch);
curl_close($ch);
return json_decode($res, true);
}
Другие подборки сниппетов
Контактная информация
Оставьте заявку или напишите напрямую в телеграм. Я отвечу в течение 24 часов.