Updates

From polling to webhook

A practical guide to switching from Long Polling to Webhook mode in PHP. Step-by-step process for setWebhook setup, secret_token generation, and error diagnostics.

Switching from Long Polling to Webhook is a standard step when migrating a Telegram bot from a local development environment to a production server. When working via getUpdates, your PHP script continuously requests updates from Telegram servers. In Webhook mode, the initiative shifts to Telegram: as soon as a user sends a message, the platform server instantly forwards it via a POST request to your HTTPS endpoint.

Why the transition is necessary

Long Polling consumes processor and network resources on endless empty requests, even when no one is using the bot. Webhook is event-driven, which saves server resources and allows for instant reaction to user actions. However, simultaneous operation of the two modes is not possible. If polling is started with an active webhook, Telegram will return an error, and messages will start to be lost or duplicated.

Step 1: Completely stop Long Polling

Before switching modes, be sure to stop all running daemons, cron jobs, or infinite CLI scripts that call getUpdates. If you don't, the old process will continue to retrieve updates from the Telegram queue, and they simply won't reach your new webhook handler.

Step 2: Register webhook via setWebhook

The setWebhook method is used to switch modes. When it is called, Telegram automatically disables polling mode for your bot. We will also need to pass a secret security token (secret_token) to protect our endpoint from third-party requests.

We use the telegramApi() function, which we created in chapter Первый запрос: getMe. Here we only call it — we do not copy the implementation.

<?php

// set_webhook.php

$token = getenv('TELEGRAM_BOT_TOKEN');
if (!$token) {
die("Ошибка: TELEGRAM_BOT_TOKEN не задан в окружении.\n");
}

// Генерируем криптографически безопасный ключ для проверки подлинности запросов от Telegram
$secretToken = bin2hex(random_bytes(16));

// В реальном проекте сохраните этот токен в .env файл или базу данных
file_put_contents(__DIR__ . '/.webhook_secret', $secretToken);

$params = [
'url' => 'https://example.com/telegram/webhook.php',
'secret_token' => $secretToken,
'drop_pending_updates' => true, // Очищает очередь старых необработанных сообщений
'allowed_updates' => json_encode(['message', 'callback_query'])
];

$response = telegramApi($token, 'setWebhook', $params);

echo "Результат выполнения setWebhook:\n";
print_r($response);

Run this script in the console: php set_webhook.php. You will see a structured array returned by the print_r() function. Example of a successful JSON response from the API that our function decoded into an array:

{
"ok": true,
"result": true,
"description": "Webhook was set"
}

Breakdown of key response fields:

  • ok: the boolean value true indicates successful execution of the HTTP request to the API.
  • result: returns true if the webhook is successfully registered in the Telegram system.
  • description: textual explanation from the server ("Webhook was set").

If the ok field returned false, pay attention to the error description in description. Typical problems: using an insecure HTTP protocol instead of HTTPS, using a non-standard port (Telegram only supports 80, 88, 443, and 8443), or problems with the SSL certificate (self-signed certificates without explicitly uploading their public key in the certificate parameter are not accepted).

The drop_pending_updates => true parameter is critically important during migration. It deletes all accumulated messages in the queue that the bot did not have time to process in polling mode. If it is not passed, Telegram will try to avalanche all old messages to your new webhook, which can lead to server overload or reprocessing of old commands.

Step 3: Check webhook status via getWebhookInfo

After successfully setting up the webhook, it is recommended to check its current state and ensure that Telegram sees your server and does not record delivery errors.

We use the telegramApi() function, which we created in chapter Первый запрос: getMe. Here we only call it — we do not copy the implementation.

<?php

// get_webhook_info.php

$token = getenv('TELEGRAM_BOT_TOKEN');
if (!$token) {
die("Ошибка: TELEGRAM_BOT_TOKEN не задан в окружении.\n");
}

$response = telegramApi($token, 'getWebhookInfo');

echo "Информация о вебхуке:\n";
print_r($response);

Run the script: php get_webhook_info.php. The current integration status will be displayed in the terminal. Example JSON response:

{
"ok": true,
"result": {
"url": "https://example.com/telegram/webhook.php",
"has_custom_certificate": false,
"pending_update_count": 0,
"max_connections": 40,
"allowed_updates": [
"message",
"callback_query"
]
}
}

Important fields for diagnostics:

  • pending_update_count: the number of updates awaiting sending in the Telegram queue. Ideally, this should be 0. If the number is growing, your webhook script is working too slowly or returning errors other than HTTP code 200 OK.
  • last_error_date and last_error_message: if network failures or server errors occurred during request delivery, the Unix time of the error and its textual description (e.g., "Connection timed out" or "HTTP status code 500") will be indicated here.
  • max_connections: the maximum number of simultaneous HTTPS connections that Telegram can open to your server (default 40).

Step 4: Reset update queue (if necessary)

If you want to temporarily disable the webhook and return to testing via polling, you will need to delete the webhook. It is useful to reset the accumulated offset on the Telegram side so that the next time polling starts, you don't receive a mountain of old messages. To do this, send one request to getUpdates with the offset = -1 parameter.

We use the telegramApi() function, which we created in chapter Первый запрос: getMe. Here we only call it — we do not copy the implementation.

<?php

// clear_queue.php

$token = getenv('TELEGRAM_BOT_TOKEN');
if (!$token) {
die("Ошибка: TELEGRAM_BOT_TOKEN не задан в окружении.\n");
}

// Сначала удаляем вебхук
telegramApi($token, 'deleteWebhook', ['drop_pending_updates' => true]);

// Подтверждаем все оставшиеся обновления в очереди
$response = telegramApi($token, 'getUpdates', [
'offset' => -1,
'limit' => 1,
'timeout'=> 0
]);

echo "Очередь очищена. Ответ API:\n";
print_r($response);

Example JSON response upon successful queue reset:

{
"ok": true,
"result": []
}

An empty array in the result field confirms that all old updates have been successfully marked as read by the Telegram server, and the queue is empty.

Checklist for a successful transition

  • The Long Polling script is completely stopped and removed from autostart.
  • Your webhook script is accessible via a public HTTPS address with a valid SSL certificate.
  • A reliable secret_token has been generated and is checked within the webhook.
  • The setWebhook method is called with the drop_pending_updates => true flag.
  • The getWebhookInfo request confirms no delivery errors (pending_update_count is 0, no errors in last_error_message).

Next: sendMessage — send the first text message to the user.

"