When developing Telegram-бот s on a local server (localhost), deploying a full-fledged HTTPS environment or using external tunnels like ngrok is not always justified. The getUpdates method, operating in Long Polling mode, allows receiving incoming events from the Telegram API without a public IP or a configured SSL certificate. Understanding how the offset and timeout parameters work helps build a reliable debugging loop and avoid duplicate commands when re-running the script.
How Long Polling Works in Telegram Bot API
Unlike classic polling (Short Polling), where the client makes frequent requests to the server and receives an immediate response (even an empty one), Long Polling keeps the HTTP connection open until a new event appears on the Telegram side or the specified timeout expires.
Two parameters are critical for getUpdates to work correctly:
- timeout — the time in seconds for which the Telegram server holds the connection open in the absence of new incoming events. The recommended value is 20 to 30 seconds.
- offset — the identifier of the first update you want to receive. To confirm the successful receipt and processing of an event batch,
offset = max(update_id) + 1is passed in the next request. This informs the Telegram API that all previous events can be removed from the queue.
If you set timeout = 0, the script will turn into resource-intensive Short Polling, causing up to 100% CPU utilization and risking a 429 Too Many Requests block from the API.
Implementing a Long Polling CLI Script in PHP
The local processing script should be run via CLI (for example, php worker.php). To interact with the networking layer of the Bot API, it is recommended to use cURL with an explicit check of the HTTP response code and validation of the received JSON document.
An important point: the cURL request timeout (CURLOPT_TIMEOUT) must exceed the timeout parameter in the Bot API by at least 5–10 seconds; otherwise, cURL will terminate the connection before the Telegram server sends a response with an HTTP 200 status.
<?php
declare(strict_types=1);
$botToken = getenv('TELEGRAM_BOT_TOKEN') ?: '';
if ($botToken === '') {
echo "Ошибка: Не задана переменная окружения TELEGRAM_BOT_TOKEN
";
exit(1);
}
$offset = 0;
$apiTimeout = 30; // Секунд ожидания события на стороне Telegram
echo "Запуск Long Polling демона...
";
while (true) {
$url = sprintf('https://api.telegram.org/bot%s/getUpdates', $botToken);
$queryParams = [
'offset' => $offset,
'timeout' => $apiTimeout,
'allowed_updates' => json_encode(['message', 'callback_query']),
];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url . '?' . http_build_query($queryParams),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $apiTimeout + 10, // cURL таймаут с запасом
CURLOPT_CONNECTTIMEOUT => 5,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($response === false) {
echo "cURL Error: {$curlError}. Повтор через 5 секунд...
";
sleep(5);
continue;
}
if ($httpCode !== 200) {
echo "HTTP Error {$httpCode}. Ответ: {$response}. Повтор через 5 секунд...
";
sleep(5);
continue;
}
$data = json_decode((string)$response, true);
if (json_last_error() !== JSON_ERROR_NONE || empty($data['ok'])) {
$description = $data['description'] ?? 'Некорректный JSON';
echo "API Error: {$description}. Повтор через 5 секунд...
";
sleep(5);
continue;
}
$updates = $data['result'] ?? [];
foreach ($updates as $update) {
$updateId = (int)$update['update_id'];
// Модифицируем offset, чтобы подтведить подтвержденные события
$offset = $updateId + 1;
echo "Обработка update_id: {$updateId}
";
// Здесь вызов бизнес-логики
}
}
Deduplication and Protection Against Reprocessing
The offset value is passed to Telegram only with the next getUpdates request. If your PHP script crashes due to a fatal error or is restarted right while executing the foreach loop, Telegram will send the exact same updates again on the next request.
To prevent business logic (for example, creating an application or sending a notification) from running twice, a deduplication mechanism at the database level is required. Before processing an event, save the update_id to storage with a unique index.
<?php
declare(strict_types=1);
function handleUpdate(array $update, PDO $pdo, string $botToken): void
{
$updateId = (int)$update['update_id'];
// 1. Атомарная защита от дублей в БД
$stmt = $pdo->prepare('INSERT IGNORE INTO processed_updates (update_id, created_at) VALUES (:id, NOW())');
$stmt->execute([':id' => $updateId]);
if ($stmt->rowCount() === 0) {
// Запись уже была добавлена ранее — пропускаем повторную обработку
return;
}
// 2. Разбор входящего события
if (isset($update['callback_query'])) {
$cq = $update['callback_query'];
$chatId = (int)$cq['message']['chat']['id'];
$callbackData = (string)($cq['data'] ?? '');
// Ограничение callback_data до 64 байт проверяется на этапе отправки кнопки
if (str_starts_with($callbackData, 'create_lead:')) {
// Генерация безопасного ID лида
$leadId = bin2hex(random_bytes(7));
$insertLead = $pdo->prepare('INSERT INTO leads (lead_code, chat_id, created_at) VALUES (:code, :chat_id, NOW())');
$insertLead->execute([':code' => $leadId, ':chat_id' => $chatId]);
sendTelegramResponse($botToken, $chatId, "Ваша заявка зарегистрирована! Код: " . $leadId);
}
}
}
function sendTelegramResponse(string $token, int $chatId, string $text): bool
{
$url = sprintf('https://api.telegram.org/bot%s/sendMessage', $token);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'chat_id' => $chatId,
'text' => $text,
'parse_mode' => 'HTML',
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$res = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($res === false || $httpCode !== 200) {
return false;
}
$data = json_decode((string)$res, true);
return !empty($data['ok']);
}
Long Polling vs Webhook: When to Switch to Production Setup
For local debugging and internal console utilities, Long Polling is ideal; however, in a production environment, it is inferior to the Webhook approach in several aspects.
- Scalability: Long Polling processes updates sequentially in a single CLI process. Webhooks work on top of a standard HTTP server (Nginx / PHP-FPM) and naturally parallelize incoming requests from users.
- Response time: With Webhooks, the Telegram server instantly sends a POST request to your controller when an event occurs. With Long Polling, a slight delay is possible due to pauses between loop iterations or the processing of heavy synchronous tasks.
- Resource management: A long-running Long Polling script requires monitoring PHP memory usage (risk of memory leaks during long execution), configuring process supervisors (Systemd, Supervisor), and handling random crashes.
Important rule: You cannot use
getUpdatesif a Webhook is already configured for the bot. When attempted, the API will return a409 Conflicterror. You must delete the Webhook before running a local script.
Common Errors When Working with getUpdates
- Error 409 Conflict when calling getUpdates: Occurs if a Webhook is active for the bot or a second instance of the Long Polling script is running with the same token. To reset the Webhook, make a GET request to
https://api.telegram.org/bot<TOKEN>/deleteWebhook. - Failure to persist offset between runs: If you restart the script without saving the current
offset(for example, in Redis or a file), it will start fetching unfulfilled updates from the very beginning of the Telegram server's history. - Script hanging on network failures: If you do not set a strict
CURLOPT_TIMEOUTin cURL, the script can hang indefinitely waiting for a response during packet loss on the router. - Running out of memory in a long-running process: When processing thousands of events within a single PHP loop, uncollected variables or in-memory loggers can trigger a
Fatal error: Allowed memory size exhausted. Always clear buffers and restart the process based on iteration counts.
If you want to entrust architecture design, configuring fault-tolerant webhooks, and integrating a Telegram bot with your CRM to professionals, contact BotCreator.