Многие разработчики начинают интеграцию с Telegram Bot API самым простым путем: вызывают file_get_contents("https://api.telegram.org/bot$token/sendMessage?..."). Это классическая ошибка, которая в продакшене быстро приводит к зависанию PHP-FPM воркеров, падению производительности сервера и уязвимостям к инъекциям. Любой сетевой сбой на стороне Telegram или задержка ответа заблокируют ваш поток выполнения.
В этой статье мы разберем, как построить отказоустойчивую отправку сообщений через sendMessage с использованием cURL, настроить жесткие таймауты, корректно обработать все типы ошибок (включая сетевые сбои, некорректный JSON и статус ok: false) и безопасно экранировать пользовательские данные при использовании parse_mode: HTML.
Почему file_get_contents не подходит для Telegram API
Функция file_get_contents отправляет GET-запрос без гибкой настройки заголовков и таймаутов. Если шлюз Telegram будет отвечать дольше обычного (например, под высокой нагрузкой), ваш PHP-скрипт зависнет до истечения дефолтного таймаута PHP (обычно 60 секунд). При плотном потоке пользователей это мгновенно исчерпает свободные воркеры веб-сервера.
Кроме того, отправка конфиденциальных данных (таких как персональные данные клиентов или токены) через GET-параметры в строке URL логируется прокси-серверами и веб-серверами по пути следования трафика. Правильный подход — отправка строго POST-запросов с телом в формате JSON через cURL.
Шаг 1. Базовый cURL-запрос с таймаутами
Для надежного сетевого взаимодействия нам понадобятся два ключевых параметра cURL: CURLOPT_CONNECTTIMEOUT (максимальное время ожидания установки соединения) и CURLOPT_TIMEOUT (максимальное время на выполнение всего запроса). Для Telegram Bot API оптимально выставлять 3-5 секунд на коннект и не более 10 секунд на получение ответа.
Ниже представлен базовый пример функции для работы с API Telegram через POST JSON:
<?php
function sendTelegramRequest(string $method, array $payload): array
{
$token = getenv("TELEGRAM_BOT_TOKEN");
if (!$token) {
throw new RuntimeException("Telegram bot token is not configured in environment variables.");
}
$url = "https://api.telegram.org/bot" . $token . "/" . $method;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 4, // 4 секунды на подключение
CURLOPT_TIMEOUT => 8, // 8 секунд лимит на весь запрос
CURLOPT_HTTPHEADER => [
'Content-Type: application/json'
],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErrno = curl_errno($ch);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlErrno !== 0) {
throw new RuntimeException("cURL Connection Error ({$curlErrno}): {$curlError}");
}
return [
'http_code' => $httpCode,
'body' => $response
];
}Шаг 2. Обработка ошибок: HTTP-коды, json_last_error и ok:false
Успешный HTTP-статус от сервера еще не означает, что сообщение доставлено. Telegram возвращает статус 200 OK при успешной отправке, но при ошибках валидации (например, неверный chat_id или сломанная HTML-разметка) он вернет HTTP-код 400 Bad Request с JSON-телом, содержащим подробности.
Алгоритм разбора ответа должен состоять из трех этапов:
- Проверка системных ошибок cURL (сеть, DNS, таймауты).
- Валидация синтаксиса JSON с помощью
json_last_error(). - Проверка внутреннего флага
okв ответе Telegram. Еслиok === false, извлекаемdescriptionиerror_code.
Вот как выглядит профессиональная обработка ответов:
<?php
function parseTelegramResponse(array $rawResult): array
{
$body = $rawResult['body'];
$httpCode = $rawResult['http_code'];
$data = json_decode($body, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException(
"Failed to parse Telegram JSON response. " .
"JSON Error: " . json_last_error_msg() . ". Raw response: " . substr($body, 0, 250)
);
}
if (!isset($data['ok']) || $data['ok'] !== true) {
$description = $data['description'] ?? 'Unknown error';
$errorCode = $data['error_code'] ?? $httpCode;
// Здесь вы можете обработать специфичные ошибки, например 429 (Too Many Requests)
throw new RuntimeException(
"Telegram API Error [Status {$errorCode}]: {$description}"
);
}
return $data['result'];
}Шаг 3. Безопасный parse_mode HTML и экранирование
При отправке сообщений с форматированием (жирный текст, курсив, ссылки) разработчики часто выбирают parse_mode => 'HTML'. Это удобно, но таит в себе скрытую угрозу. Если в тексте сообщения окажется спецсимвол (например, угловая скобка <, > или амперсанд &), Telegram не сможет распарсить разметку и вернет ошибку 400 Bad Request: can't parse entities, а пользователь не получит уведомление.
Чтобы этого избежать, любой динамический контент (имена пользователей, тексты отзывов, значения из БД) необходимо экранировать с помощью htmlspecialchars() со строгими флагами. При этом статические теги оформления (такие как <b>, <i>, <code>) должны оставаться нетронутыми.
<?php
function safeHtml(string $text): string
{
// Экранируем символы <, >, &, " и ' в соответствии с требованиями Telegram HTML
return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
}
// Пример формирования безопасного сообщения
$userName = "<Ivan> & Co";
$userComment = "Хочу заказать разработку <strong>срочно</strong>!";
$messageText = "<b>Новая заявка!</b>\n\n" .
"Клиент: " . safeHtml($userName) . "\n" .
"Комментарий: <code>" . safeHtml($userComment) . "</code>";
// Результат безопасен для отправки, парсер Telegram не упадет.Шаг 4. Объединяем все в класс-клиент для продакшена
Соберем все лучшие практики в один лаконичный класс, который можно легко внедрить в любой PHP-проект, будь то чистый PHP, Laravel или контроллер Yii2.
<?php
class TelegramNotifier
{
private string $token;
public function __construct()
{
$this->token = (string) getenv('TELEGRAM_BOT_TOKEN');
if (empty($this->token)) {
throw new InvalidArgumentException("Telegram Bot Token environment variable is missing.");
}
}
public function sendMessage(int $chatId, string $htmlText): bool
{
$payload = [
'chat_id' => $chatId,
'text' => $htmlText,
'parse_mode' => 'HTML',
'disable_web_page_preview' => true
];
try {
$url = "https://api.telegram.org/bot" . $this->token . "/sendMessage";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_TIMEOUT => 7,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErrno = curl_errno($ch);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlErrno !== 0) {
error_log("Telegram cURL Network Error ({$curlErrno}): {$curlError}");
return false;
}
$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("Telegram JSON Decode Error: " . json_last_error_msg() . ". Response: " . $response);
return false;
}
if (!isset($data['ok']) || !$data['ok']) {
$desc = $data['description'] ?? 'No description';
$code = $data['error_code'] ?? $httpCode;
error_log("Telegram API Error [{$code}]: {$desc} (Chat ID: {$chatId})");
return false;
}
return true;
} catch (Throwable $e) {
error_log("Fatal error in TelegramNotifier: " . $e->getMessage());
return false;
}
}
}Заключение
Для надежной отправки сообщений в Telegram Bot API всегда используйте cURL с POST JSON-телом вместо небезопасного file_get_contents. Обязательно ограничивайте таймаут соединения (connect timeout) и общее время выполнения, чтобы не забивать ресурсы сервера. Применение htmlspecialchars для динамических данных убережет ваших ботов от падений из-за некорректного синтаксиса разметки HTML.
Если вам требуется разработка отказоустойчивых Telegram-решений, интеграция с CRM-системами и автоматизация бизнес-процессов, доверьте это профессионалам из BotCreator.