When integrating Telegram notifications into a PHP application, developers often rely on basic file_get_contents calls. While simple, this approach lacks proper timeout management, fails to handle HTTP status codes gracefully, and makes it difficult to debug API-level errors.
This tutorial demonstrates how to build a production-ready wrapper for the Telegram Bot API sendMessage endpoint using native PHP and cURL. We do not claim this is a full-featured SDK; instead, it is a robust, single-purpose implementation designed to be pasted directly into your staging or production environments.
The Core Implementation
To interact with the Telegram Bot API reliably, we must configure explicit connection and execution timeouts, verify the HTTP response status, validate that the response is valid JSON, and inspect the ok field returned by Telegram.
Here is the complete helper function:
<?php
declare(strict_types=1);
/**
* Sends a message to a Telegram chat via the Bot API.
*
* @param string $token The Telegram Bot token.
* @param int|string $chatId The target chat ID.
* @param string $text The message text.
* @param string $parseMode The formatting mode (HTML, MarkdownV2, etc.).
* @return array The decoded Telegram response.
* @throws RuntimeException If the request fails, times out, or Telegram returns an error.
*/
function sendTelegramMessage(string $token, int|string $chatId, string $text, string $parseMode = 'HTML'): array
{
$url = "https://api.telegram.org/bot" . $token . "/sendMessage";
$payload = [
'chat_id' => $chatId,
'text' => $text,
'parse_mode' => $parseMode,
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Prevent the script from hanging indefinitely
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
curl_close($ch);
throw new RuntimeException("cURL Error: " . $error);
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException(
"Failed to decode JSON response. Raw response: " . $response . ". JSON Error: " . json_last_error_msg()
);
}
if (!isset($data['ok']) || $data['ok'] !== true) {
$description = $data['description'] ?? 'Unknown error';
$errorCode = $data['error_code'] ?? $httpCode;
throw new RuntimeException("Telegram API Error [{$errorCode}]: {$description}", $errorCode);
}
return $data;
}
Formatting with HTML and Escaping Input
When using parse_mode = 'HTML', Telegram requires strict compliance with its supported HTML tags (<b>, <i>, <code>, etc.). If your message contains unescaped characters like <, >, or & that do not form valid, supported tags, Telegram will reject the payload with a 400 Bad Request error.
To prevent this, always pass dynamic user input through htmlspecialchars before interpolating it into your message template.
<?php
require_once 'telegram_helper.php';
// Retrieve the token from environment variables
$token = getenv('TELEGRAM_BOT_TOKEN');
$chatId = getenv('TELEGRAM_CHAT_ID');
if (!$token || !$chatId) {
die("Configuration error: TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID must be set.\n");
}
// Dynamic user input that might contain special characters
$userInput = "User <John&Doe> Testing";
// Escape the input safely for HTML parse_mode
$safeInput = htmlspecialchars($userInput, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
$message = "<b>System Alert</b>\n";
$message .= "Status: <i>Active</i>\n";
$message .= "Payload: <code>" . $safeInput . "</code>";
try {
$result = sendTelegramMessage($token, $chatId, $message, 'HTML');
echo "Message sent successfully. Message ID: " . $result['result']['message_id'] . "\n";
} catch (RuntimeException $e) {
echo "Error sending message: " . $e->getMessage() . "\n";
}
Production Considerations
Handling Rate Limits (HTTP 429)
If your application sends a high volume of messages, Telegram may return an HTTP status code 429 (Too Many Requests). The response body will contain a retry_after field indicating how many seconds you must wait before retrying. In a production queue worker, you should catch the RuntimeException, inspect the error code, and delay the job execution accordingly.
Handling Blocked Bots (HTTP 403)
If a user has blocked your bot or has not started a conversation with it, Telegram will return an HTTP 403 Forbidden with the description "Forbidden: bot was blocked by the user". Your application should catch this specific error and mark the user's chat ID as inactive in your database to avoid making unnecessary API calls.
Timeouts
We configured CURLOPT_CONNECTTIMEOUT to 5 seconds and CURLOPT_TIMEOUT to 10 seconds. These values ensure that if Telegram's servers experience latency, your PHP process (such as an FPM worker) does not hang indefinitely, which could otherwise lead to resource exhaustion on your web server.
For further details on available parameters and formatting rules, refer to the official documentation at https://botservice.biz/telegram-bot-api.
BotCreator — studio that ships Telegram bots / Mini Apps.
Top comments (0)