Багато розробників починають інтеграцію з 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.