Deploying a Telegram bot into a production environment requires more than just an HTTP endpoint to accept incoming updates. Without structured console tooling, developers fall back on firing manual curl requests from local terminals to register webhooks or diagnose API failures. This ad-hoc approach frequently leads to missing webhook secret tokens, unhandled HTTP timeouts, and misconfigured SSL endpoints. Furthermore, applications that log raw Telegram update payloads for audit or idempotency purposes will eventually run into database performance degradation if those tables are left to grow infinitely.
A dedicated Yii2 console controller solves these operational challenges by standardizing deployment actions and maintenance tasks. By encapsulating Telegram Bot API operations inside console commands, you can integrate webhook management into CI/CD deployment pipelines, verify API connectivity programmatically, and execute scheduled database log rotation without manual intervention.
Why Manual Webhook Setup and Unmanaged Logs Fail
When you manage a bot's lifecycle manually using browser tabs or arbitrary cURL commands, several subtle failure modes emerge:
- Unvalidated Bot API Responses: The Telegram Bot API always returns an HTTP
200 OKstatus code for well-formed HTTP requests, even when the underlying API call fails (for instance, when an invalid bot token is provided or when an invalid URL is passed tosetWebhook). A naive curl script that only checks the HTTP response code will mask critical configuration errors during deployment. - Missing Webhook Secret Tokens: Telegram allows developers to set a
secret_tokenheader (between 1 and 256 characters) when registering a webhook. If you set up webhooks manually, it is easy to forget this step, leaving your webhook endpoint vulnerable to spoofed HTTP POST requests from external bad actors. - Database Locks During Log Cleanup: Storing incoming updates in a MySQL or PostgreSQL table is essential for idempotency and debugging. However, running a naive
DELETE FROM telegram_update WHERE created_at < NOW() - INTERVAL 30 DAYquery on a table containing millions of rows will acquire heavy row or table locks, blocking incoming webhook writes and causing HTTP 504 timeouts on Telegram's servers.
To prevent these issues, we can build a unified TelegramController extending yii\console\Controller that handles operational checks, webhook configuration, and chunked log deletion.
Building the Yii2 Bot Console Controller
First, ensure your application has the yii2-httpclient extension installed. We will use yii\httpclient\Client to manage HTTP communication with Telegram's servers, enforcing strict request timeouts and explicitly evaluating both the HTTP status code and Telegram's internal ok boolean flag.
Create the file commands/TelegramController.php in your Yii2 console application structure:
<?php namespace app\commands; use Yii; use yii\console\Controller; use yii\console\ExitCode; use yii\helpers\Console; use yii\httpclient\Client; class TelegramController extends Controller { /** * @var string Configurable CLI parameter for log rotation age in days */ public $days = 30; /** * @var int Configurable CLI parameter for batch deletion size */ public $batchSize = 1000; public function options($actionID) { $options = parent::options($actionID); if ($actionID === 'rotate-logs') { $options[] = 'days'; $options[] = 'batchSize'; } return $options; } /** * Check Telegram Bot API connectivity via getMe. */ public function actionHealth() { $token = $this->getBotToken(); if (!$token) { return ExitCode::CONFIG; } $client = new Client(['baseUrl' => 'https://api.telegram.org/bot' . $token]); try { $response = $client->createRequest() ->setMethod('GET') ->setUrl('getMe') ->getOptions()[CURLOPT_TIMEOUT = 5] ->send(); } catch (\Exception $e) { $this->stdout("HTTP request failed: " . $e->getMessage() . " ", Console::FG_RED); return ExitCode::UNAVAILABLE; } if (!$response->isOk) { $this->stdout("HTTP Error Status: " . $response->statusCode . " ", Console::FG_RED); return ExitCode::UNAVAILABLE; } $data = $response->data; if (empty($data['ok']) || $data['ok'] !== true) { $description = $data['description'] ?? 'Unknown API error'; $this->stdout("Telegram API Error: {$description} ", Console::FG_RED); return ExitCode::DATAERR; } $username = $data['result']['username'] ?? 'Unknown'; $botId = $data['result']['id'] ?? 'Unknown'; $this->stdout("Bot Health OK: @{$username} (ID: {$botId}) ", Console::FG_GREEN); return ExitCode::OK; } /** * Register the production webhook URL with a secret token. */ public function actionSetWebhook() { $token = $this->getBotToken(); $webhookUrl = Yii::$app->params['telegram']['webhookUrl'] ?? getenv('TELEGRAM_WEBHOOK_URL'); $secretToken = Yii::$app->params['telegram']['secretToken'] ?? getenv('TELEGRAM_SECRET_TOKEN'); if (!$token || !$webhookUrl || !$secretToken) { $this->stdout("Error: TELEGRAM_BOT_TOKEN, TELEGRAM_WEBHOOK_URL, and TELEGRAM_SECRET_TOKEN must be set. ", Console::FG_RED); return ExitCode::CONFIG; } $client = new Client(['baseUrl' => 'https://api.telegram.org/bot' . $token]); $payload = [ 'url' => $webhookUrl, 'secret_token' => $secretToken, 'allowed_updates' => ['message', 'callback_query', 'my_chat_member'], 'drop_pending_updates' => false, ]; try { $response = $client->createRequest() ->setMethod('POST') ->setUrl('setWebhook') ->setData($payload) ->send(); } catch (\Exception $e) { $this->stdout("Network error while setting webhook: " . $e->getMessage() . " ", Console::FG_RED); return ExitCode::UNAVAILABLE; } $data = $response->data; if (!$response->isOk || empty($data['ok']) || $data['ok'] !== true) { $error = $data['description'] ?? 'Failed to set webhook'; $this->stdout("Failed to set webhook: {$error} ", Console::FG_RED); return ExitCode::SOFTWARE; } $this->stdout("Webhook successfully set to: {$webhookUrl} ", Console::FG_GREEN); return ExitCode::OK; } /** * Remove the registered webhook. */ public function actionDeleteWebhook() { $token = $this->getBotToken(); if (!$token) { return ExitCode::CONFIG; } $client = new Client(['baseUrl' => 'https://api.telegram.org/bot' . $token]); try { $response = $client->createRequest() ->setMethod('POST') ->setUrl('deleteWebhook') ->setData(['drop_pending_updates' => false]) ->send(); } catch (\Exception $e) { $this->stdout("Network error while deleting webhook: " . $e->getMessage() . " ", Console::FG_RED); return ExitCode::UNAVAILABLE; } $data = $response->data; if (!$response->isOk || empty($data['ok']) || $data['ok'] !== true) { $error = $data['description'] ?? 'Failed to delete webhook'; $this->stdout("Failed to delete webhook: {$error} ", Console::FG_RED); return ExitCode::SOFTWARE; } $this->stdout("Webhook deleted successfully. ", Console::FG_GREEN); return ExitCode::OK; } private function getBotToken(): ?string { $token = Yii::$app->params['telegram']['botToken'] ?? getenv('TELEGRAM_BOT_TOKEN'); if (empty($token)) { $this->stdout("Error: TELEGRAM_BOT_TOKEN environment variable or param is missing. ", Console::FG_RED); return null; } return $token; } } In this implementation, every command checks for key environment variables or application parameters, executes the request using yii\httpclient\Client, and evaluates both $response->isOk (HTTP status check) and $data['ok'] (Telegram API logical success check). Console error codes (ExitCode::OK, ExitCode::CONFIG, ExitCode::UNAVAILABLE) are explicitly returned so continuous integration runners can block deployment pipeline steps if the API is unreachable.
Implementing Safe Update Log Rotation in Yii2
If your application logs raw Telegram updates to a database table named telegram_update to prevent duplicate processing or to keep an audit trail, purging old data is essential.
Running an unindexed or massive single DELETE statement causes high disk I/O, potential table lock escalation, and database replication lag. To prevent locking the telegram_update table during production hours, the rotation command must purge old records in small, controlled batches using a chunked loop.
Add the actionRotateLogs method to your TelegramController:
/** * Prune update logs older than specified days in chunked batches. */ public function actionRotateLogs() { $days = (int)$this->days; $batchSize = (int)$this->batchSize; if ($days < 1 || $batchSize < 10) { $this->stdout("Invalid parameters. Days must be >= 1 and batchSize >= 10. ", Console::FG_RED); return ExitCode::DATAERR; } $cutoffTimestamp = time() - ($days * 86400); $cutoffDate = date('Y-m-d H:i:s', $cutoffTimestamp); $db = Yii::$app->db; $totalDeleted = 0; $this->stdout("Starting log rotation for records older than {$cutoffDate}... ", Console::FG_YELLOW); while (true) { // Find primary keys of rows to delete in this batch $ids = (new \yii\db\Query()) ->select(['id']) ->from('{{%telegram_update}}') ->where(['<', 'created_at', $cutoffDate]) ->limit($batchSize) ->column($db); if (empty($ids)) { break; } // Execute batch deletion matching primary key array $deletedCount = $db->createCommand() ->delete('{{%telegram_update}}', ['id' => $ids]) ->execute(); $totalDeleted += $deletedCount; $this->stdout("Purged batch of {$deletedCount} records (Total: {$totalDeleted})... "); // Pause briefly to release DB locks and yield execution time usleep(50000); // 50ms pause } $this->stdout("Log rotation finished. Successfully purged {$totalDeleted} records. ", Console::FG_GREEN); return ExitCode::OK; } This method fetches primary key values (id) matching the timestamp condition up to $batchSize. It then deletes only those explicit primary keys. This guarantees that each DELETE statement executes in milliseconds, keeping index lock duration negligible and allowing concurrent webhooks to continue inserting incoming updates without hitting database deadlock timeouts.
Ensure that the underlying telegram_update table has a compound or individual index on created_at:
ALTER TABLE `telegram_update` ADD INDEX `idx_telegram_update_created_at` (`created_at`); Without an index on created_at, the subquery selecting candidate IDs will perform a full table scan on every batch iteration, defeating the performance benefits of chunked deletion.
Wiring Commands into CI/CD and System Cron
Once your console actions are defined, you can incorporate them into deployment scripts and server cron routines.
Deployment Script Example (deploy.sh)
In your deployment pipeline, call the console commands after applying database migrations and clearing application cache:
#!/usr/bin/env bash set -e echo "Running database migrations..." php yii migrate --interactive=0 echo "Verifying Telegram Bot API connection..." php yii telegram/health echo "Registering production Telegram Webhook..." php yii telegram/set-webhook echo "Deployment finished successfully." By adding set -e in bash, if php yii telegram/health returns a non-zero exit code (such as ExitCode::UNAVAILABLE or ExitCode::CONFIG), the deployment script will immediately halt before routing traffic to an misconfigured application.
Crontab Schedule Example
To run log cleanup automatically every night at 02:00 AM without blocking system resources, add the following entry to your system crontab:
0 2 * * * /usr/bin/php /var/www/my-yii2-app/yii telegram/rotate-logs --days=14 --batchSize=500 >> /var/www/my-yii2-app/runtime/logs/cron-telegram.log 2>&1 Production Notes
- Secret Token Verification: When registering a webhook via
telegram/set-webhook, you passsecret_token. On the incoming HTTP webhook controller side (e.g.,actionWebhook), you must verify thatYii::$app->request->getHeaders()->get('X-Telegram-Bot-Api-Secret-Token') === $configuredSecretToken. Reject any request missing this header with an HTTP403 Forbiddenstatus immediately. - Network Timeouts: Always specify explicit timeouts when communicating with Telegram API endpoints. While
setWebhookanddeleteWebhooktypically finish within a few hundred milliseconds, unmanaged cURL connections can hang indefinitely if intermediary network routes drop packets. - Log Rotation Memory Usage: The chunked deletion logic selects primary key columns into array memory using
column(). Keep--batchSizebetween500and2000to balance execution speed with PHP memory consumption.
Building custom console automation around your Telegram bot setup prevents configuration drift between staging and production environments, eliminates runtime database bottlenecks, and guarantees that API failures are exposed early in your deployment cycle.
When building complex automated Telegram integrations or Mini Apps that demand robust architectural pipelines, working with specialized developers speeds up delivery. BotCreator provides experienced engineering teams to build and scale secure Telegram applications.

