Skip to content
← К сниппетам
JavaScriptPHP

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 не определён, и бот падает. Приведение апдейта к одному объекту пользователя на входе снимает целый класс плавающих ошибок.

t.me/yourbot?start=<payload> — это единственное, что Telegram передаст боту до начала разговора, поэтому именно так атрибутируют регистрацию, возобновляют оплату или связывают чат с записью в своей базе. Значение приходит текстом после /start, ограничено 64 символами и допускает только URL-безопасный алфавит — то есть туда кладут токен или идентификатор, а не сериализованный объект.

Экранирование MarkdownV2 строже, чем кажется

MarkdownV2 требует экранировать восемнадцать символов, включая те, что постоянно встречаются в обычном тексте: ., -, !, (, ), +. Пропущенный символ приводит не к отправке без форматирования, а к отказу API с ошибкой 400 — то есть одна неэкранированная точка в пользовательском тексте роняет весь ответ. Внутри блоков кода и в адресах ссылок правила экранирования другие, и самописные экранировщики обычно ломаются именно там.

Клавиатуры и callback

Inline-клавиатура — это массив рядов, и Telegram не раскладывает её за вас: ряд из шести кнопок так и останется рядом из шести нечитаемых кнопок на телефоне. Отвечать на callback тоже обязательно: пока не вызван answerCallbackQuery, клиент показывает на кнопке крутилку, а сам вызов должен уложиться в несколько секунд после нажатия.

Каждый сниппет ниже самодостаточен — берите нужный. Вместе они складываются в рабочий скелет цикла обработки запроса.

JavaScriptJavaScript

Разбор 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 }
JavaScriptJavaScript

Автоматическая раскладка инлайн-кнопок Telegram

Упаковывает короткие кнопки по две в ряд, а длинным даёт отдельный ряд — в зависимости от длины текста сообщения над ними, сохраняя клавиатуру компактной без обрезания подписей.

Как это работает: инлайн-клавиатуры Telegram имеют фиксированную визуальную ширину. Когда сообщение выше короткое, одна кнопка в ряд читается лучше; когда оно достаточно длинное и вертикальное пространство важно, соседние короткие кнопки (вроде "Да"/"Нет") жадно объединяются в один ряд, а длинные остаются сами по себе.

const SHORT_CONTENT_THRESHOLD = 35
const SHORT_BUTTON_THRESHOLD = 10

function layoutTelegramButtons(buttons, contentLength) {
  if (buttons.length <= 1) return buttons.map((b) => [b])

  if (contentLength < SHORT_CONTENT_THRESHOLD) {
    return buttons.map((b) => [b])
  }

  const rows = []
  let i = 0
  while (i < buttons.length) {
    const current = buttons[i]
    const next = buttons[i + 1]
    const bothShort = next && current.text.length <= SHORT_BUTTON_THRESHOLD && next.text.length <= SHORT_BUTTON_THRESHOLD

    if (bothShort) {
      rows.push([current, next])
      i += 2
    } else {
      rows.push([current])
      i += 1
    }
  }
  return rows
}

// Example usage — feed straight into Telegram's inline_keyboard
const inline_keyboard = layoutTelegramButtons(
  [{ text: 'Yes', callback_data: 'y' }, { text: 'No', callback_data: 'n' }],
  120
)
// [[{text:'Yes',...}, {text:'No',...}]] — paired into one row
JavaScriptJavaScript

Ответ на 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,
})
JavaScriptJavaScript

Получить 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
PHP

Экранирование текста для 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',
]);
PHP

Получить 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
PHP

Отправка сообщения через 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'],
        ]],
    ]),
]);
PHP

Верификация 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);
}
// CONNECT

Контактная информация

Оставьте заявку или напишите напрямую в телеграм. Я отвечу в течение 24 часов.

Укажите email или Telegram, чтобы я мог вам ответить.