Managing Telegram Bot API integrations requires reliable administrative tooling. While webhooks handle incoming user payloads in real time, setting up, inspecting, and maintaining those webhooks should be handled outside the HTTP web server's request lifecycle. Using a CLI tool allows you to perform deployment steps, health checks, and maintenance tasks reliably without web server timeouts or public exposure.
In this tutorial, we will build a dedicated Yii2 console controller (commands/TelegramController.php) that interacts directly with the Telegram Bot API over cURL. We will implement four essential operations: testing bot credentials with getMe, registering a public webhook URL using setWebhook (including secret_token injection), removing webhooks via deleteWebhook, and pruning historical raw update logs from local disk storage.
This guide focuses strictly on administrative lifecycle management and update log retention. It does not cover writing a full conversational routing engine, handling long-polling daemons, or building a message UI.
---
Step 1: Configure Telegram API Parameters
Store API tokens in environment variables or Yii2 application parameters. Avoid hardcoding tokens inside controller code or version-controlled configuration files.
Add your configuration to config/params.php:
<?php
return [
'telegram.botToken' => getenv('TELEGRAM_BOT_TOKEN') ?: '',
'telegram.secretToken' => getenv('TELEGRAM_SECRET_TOKEN') ?: '',
'telegram.webhookUrl' => getenv('TELEGRAM_WEBHOOK_URL') ?: '',
'telegram.logDir' => '@runtime/telegram-logs',
];
Ensure your config/console.php enables the command namespace and correctly loads params.php:
<?php
$params = require __DIR__ . '/params.php';
$config = [
'id' => 'basic-console',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'controllerNamespace' => 'app\commands',
'components' => [
'log' => [
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
],
'params' => $params,
];
return $config;
---
Step 2: Implement the Console Controller
Create commands/TelegramController.php. This class handles API communications using standard PHP cURL functions. It validates HTTP status codes, decodes JSON structures, handles API-level failure payloads ("ok": false), and logs error output directly to Console::error.
<?php
namespace app\commands;
use Yii;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\Console;
use yii\helpers\FileHelper;
class TelegramController extends Controller
{
/**
* @var string Output formatting verbosity level.
*/
public $defaultAction = 'health';
/**
* Executes getMe to verify bot token validity and connection health.
*/
public function actionHealth(): int
{
$this->stdout("Checking Telegram API connectivity...
", Console::FG_BLUE);
$response = $this->sendApiRequest('getMe');
if (!$response['ok']) {
$this->stderr("Health Check Failed: {$response['description']}
", Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
$bot = $response['result'];
$this->stdout("Bot ID: {$bot['id']}
", Console::FG_GREEN);
$this->stdout("Username: @{$bot['username']}
", Console::FG_GREEN);
$this->stdout("Can Join Groups: " . ($bot['can_join_groups'] ? 'Yes' : 'No') . "
");
$this->stdout("Can Read All Group Messages: " . ($bot['can_read_all_group_messages'] ? 'Yes' : 'No') . "
");
return ExitCode::OK;
}
/**
* Registers a webhook URL with Telegram.
*
* @param string|null $url Custom webhook URL. Defaults to params configuration.
*/
public function actionSetWebhook(?string $url = null): int
{
$targetUrl = $url ?? Yii::$app->params['telegram.webhookUrl'];
$secretToken = Yii::$app->params['telegram.secretToken'];
if (empty($targetUrl)) {
$this->stderr("Error: Webhook URL is not specified.
", Console::FG_RED);
return ExitCode::DATAERR;
}
$params = [
'url' => $targetUrl,
'max_connections' => 40,
'allowed_updates' => ['message', 'callback_query', 'my_chat_member'],
];
if (!empty($secretToken)) {
$params['secret_token'] = $secretToken;
}
$this->stdout("Setting webhook to: {$targetUrl}
");
$response = $this->sendApiRequest('setWebhook', $params);
if (!$response['ok']) {
$this->stderr("Failed to set webhook: {$response['description']}
", Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
$this->stdout("Success: {$response['result']}
", Console::FG_GREEN);
return ExitCode::OK;
}
/**
* Deletes the currently registered webhook.
*
* @param bool $dropPending Whether to drop pending updates stored on Telegram servers.
*/
public function actionDeleteWebhook(bool $dropPending = false): int
{
$this->stdout("Removing webhook configuration...
");
$params = [
'drop_pending_updates' => $dropPending,
];
$response = $this->sendApiRequest('deleteWebhook', $params);
if (!$response['ok']) {
$this->stderr("Failed to delete webhook: {$response['description']}
", Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
$this->stdout("Webhook successfully deleted.
", Console::FG_GREEN);
return ExitCode::OK;
}
/**
* Prunes update log files older than a specified number of days.
*
* @param int $days Number of days to keep logs.
*/
public function actionRotateLogs(int $days = 7): int
{
$logDir = Yii::getAlias(Yii::$app->params['telegram.logDir']);
if (!is_dir($logDir)) {
$this->stdout("Log directory does not exist: {$logDir}
", Console::FG_YELLOW);
return ExitCode::OK;
}
$cutoffTimestamp = time() - ($days * 86400);
$files = FileHelper::findFiles($logDir, ['only' => ['*.json', '*.log']]);
$deletedCount = 0;
foreach ($files as $file) {
if (filemtime($file) < $cutoffTimestamp) {
if (@unlink($file)) {
$deletedCount++;
} else {
$this->stderr("Could not delete file: {$file}
", Console::FG_RED);
}
}
}
$this->stdout("Log cleanup complete. Removed {$deletedCount} log files older than {$days} days.
", Console::FG_GREEN);
return ExitCode::OK;
}
/**
* Helper method to send HTTP Requests to Telegram Bot API via cURL.
*/
private function sendApiRequest(string $method, array $params = []): array
{
$token = Yii::$app->params['telegram.botToken'];
if (empty($token)) {
return [
'ok' => false,
'description' => 'TELEGRAM_BOT_TOKEN parameter is missing or empty.',
];
}
$url = "https://api.telegram.org/bot{$token}/{$method}";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$rawResponse = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($rawResponse === false) {
return [
'ok' => false,
'description' => "cURL network error: {$curlError}",
];
}
$decoded = json_decode($rawResponse, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return [
'ok' => false,
'description' => "Failed to parse API response JSON. Raw output: " . substr($rawResponse, 0, 100),
];
}
if ($httpCode !== 200 && !isset($decoded['description'])) {
$decoded['description'] = "HTTP response status code {$httpCode}";
}
return $decoded;
}
}
---
Step 3: Command Usage & Deployment Integration
With the controller in place, test your commands using the standard yii CLI executable.
#### 1. Checking API Credentials Run the health command to verify that your TELEGRAM_BOT_TOKEN is correct and network access to api.telegram.org is unimpeded:
php yii telegram/health
Expected output:
Checking Telegram API connectivity...
Bot ID: 123456789
Username: @MyProductionBot
Can Join Groups: Yes
Can Read All Group Messages: No
#### 2. Registering Webhooks for Staging or Production Deployments can trigger set-webhook automatically in continuous integration pipelines. You can pass the webhook target URL as an explicit parameter or let Yii2 pick it up from your .env configuration:
php yii telegram/set-webhook "https://example.com/telegram/webhook"
If the request succeeds, Telegram returns true, and your defined secret header is bound to all incoming POST payloads sent by Telegram.
#### 3. Maintenance or Maintenance Mode Webhook Deletion When putting your application into maintenance mode or migrating server environments, run delete-webhook. To discard queued incoming updates during maintenance windows, pass the --dropPending=1 flag:
php yii telegram/delete-webhook 1
#### 4. Automated Log Rotation via System Cron If your webhook controller writes raw JSON payloads to local log files (for example, inside @runtime/telegram-logs/YYYY-MM-DD.json), you must clean up old logs to keep disk usage under control. Configure a system cron job on your server to execute log rotation daily:
# /etc/cron.d/telegram-maintenance
0 3 * * * www-data /usr/bin/php /var/www/my-app/yii telegram/rotate-logs 14 > /dev/null 2>&1
This schedule executes every night at 3:00 AM, purging update files older than 14 days without manual intervention.
---
Production & Security Considerations
1. Enforce secret_token Validation: Always pass a secure secret_token string containing 1 to 256 alphanumeric characters or underscores when calling setWebhook. When processing incoming HTTP POST requests in your web controller, confirm that the header X-Telegram-Bot-Api-Secret-Token matches this secret value using hash_equals(). 2. Restrict Update Types: Use the allowed_updates array parameter in setWebhook to specify only the event payloads your app explicitly handles (e.g., ['message', 'callback_query']). Restricting types prevents Telegram from delivering unnecessary payload categories, reducing network load and execution overhead. 3. Timeouts and Retries: Set strict timeouts (CURLOPT_TIMEOUT set to 10 seconds or less) in your administrative API calls. If Telegram experiences partial service degradation, CLI maintenance tasks should fail quickly rather than blocking deployment scripts or background worker queues indefinitely. 4. Permissions: Ensure that the system user executing the console command (e.g., www-data or deploy) has read and write permissions on @runtime/telegram-logs when running the rotate-logs action.
If you need a team to handle custom Telegram integration architecture, webhooks, and Mini App backend infrastructure, consider working with BotCreator.