PHP & frameworks
Laravel
Native Telegram Bot API integration in Laravel: service class using the built-in HTTP client, webhook controller, Artisan command for registration, queues for sending messages.
Laravel allows you to integrate the Telegram Bot API in two ways: via the built-in HTTP client (a wrapper over Guzzle) or ready-made packages like irazasyed/telegram-bot-sdk. Below is a native option without extra dependencies, as close as possible to the approach from article HTTP-клиент на PHP. We will extract the network logic into a service class, keeping the controller only for parsing the update and calling business logic.
Service Class for Outgoing Requests
Create app/Services/TelegramBot.php. In the constructor, we read the token from the config (it is taken from getenv('TELEGRAM_BOT_TOKEN') via config/services.php). The call() method is a universal wrapper: it makes a POST request to api.telegram.org/bot<token>/<method>, checks the HTTP code, json_last_error, and the ok field in the response. On error, it throws RuntimeException with a clear message.
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
class TelegramBot
{
protected string $token;
protected string $baseUrl;
public function __construct(?string $token = null)
{
$this->token = $token ?? config('services.telegram.token');
$this->baseUrl = "https://api.telegram.org/bot{$this->token}";
}
/**
* Универсальный вызов метода Bot API.
* @throws \RuntimeException при сетевой ошибке или ok=false
*/
public function call(string $method, array $params = []): array
{
try {
$response = Http::timeout(15)
->retry(3, 100)
->post("{$this->baseUrl}/{$method}", $params)
->throw()
->json();
} catch (ConnectionException $e) {
throw new \RuntimeException("Telegram API connection failed: " . $e->getMessage());
} catch (RequestException $e) {
$body = $e->response?->json();
$desc = $body['description'] ?? $e->getMessage();
throw new \RuntimeException("Telegram API error: {$desc}");
}
if (empty($response['ok'])) {
throw new \RuntimeException("Telegram API returned ok=false: " . ($response['description'] ?? 'unknown'));
}
return $response['result'];
}
public function sendMessage(int|string $chatId, string $text, array $options = []): array
{
return $this->call('sendMessage', array_merge([
'chat_id' => $chatId,
'text' => $text,
'parse_mode' => 'HTML',
], $options));
}
public function answerCallbackQuery(string $callbackQueryId, ?string $text = null, bool $showAlert = false): array
{
return $this->call('answerCallbackQuery', [
'callback_query_id' => $callbackQueryId,
'text' => $text,
'show_alert' => $showAlert,
]);
}
}
API Response Example and How to Inspect It
The sendMessage method returns a Message object. In code, you can check the result like this:
<?php
$result = $bot->sendMessage(123456789, "Тестовое сообщение");
// Для отладки в консоли (php artisan tinker или job):
var_dump($result);
// Или красиво:
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
A typical JSON response (field result):
{
"message_id": 42,
"date": 1700000000,
"chat": {
"id": 123456789,
"type": "private",
"first_name": "Ivan"
},
"text": "Тестовое сообщение"
}
What to look at: message_id — needed for subsequent editing/deletion (methods editMessageText, deleteMessage); chat.id — confirms that the message was sent right where planned; date — unix timestamp of sending according to the Telegram server.
Token Configuration
Do not hardcode the token. In config/services.php, read it from the environment:
// config/services.php
return [
// ...
'telegram' => [
'token' => env('TELEGRAM_BOT_TOKEN'),
],
];
# .env
TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
You can check that the token was picked up properly in Tinker:
php artisan tinker
> config('services.telegram.token')
Controller for Incoming Updates (Webhook)
Create app/Http/Controllers/TelegramWebhookController.php. Laravel automatically decodes the JSON body into an array $request->all(). In chapter Long polling, we made the processUpdate function — the logic here is the same, just inside a controller. Important: always return 200 OK with {"ok":true} within 1–2 seconds, otherwise Telegram will retry delivery.
<?php
namespace App\Http\Controllers;
use App\Services\TelegramBot;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class TelegramWebhookController extends Controller
{
public function __invoke(Request $request, TelegramBot $bot): JsonResponse
{
$update = $request->all();
// Защита от пустых/некорректных запросов
if (!isset($update['update_id'])) {
return response()->json(['ok' => true]);
}
// --- Сообщения ---
if (isset($update['message'])) {
$msg = $update['message'];
$chatId = $msg['chat']['id'];
$text = $msg['text'] ?? '';
// В главе "Команды /start, /help" мы делали parseCommand — используем тот же подход
if (str_starts_with($text, '/start')) {
$bot->sendMessage($chatId, "Привет! Я бот на Laravel.\nНапиши /help для списка команд.");
} elseif ($text === '/help') {
$bot->sendMessage($chatId, "Доступные команды: /start, /help");
}
}
// --- Callback queries (inline-кнопки) ---
if (isset($update['callback_query'])) {
$cq = $update['callback_query'];
// В главе "callback_query" мы разбирали answerCallbackQuery — здесь вызываем метод сервиса
$bot->answerCallbackQuery($cq['id'], "Нажата кнопка: {$cq['data']}");
}
return response()->json(['ok' => true]);
}
}
Route and Webhook Registration
In routes/web.php (or api.php), add a route. The {token} parameter in the URL provides basic protection against random requests; in the controller, it can be checked against config('services.telegram.token').
use App\Http\Controllers\TelegramWebhookController;
Route::post('/telegram/webhook/{token}', TelegramWebhookController::class)
->where('token', '.*');
Set the webhook once — via an Artisan command (convenient for deployment) or in Tinker. The command uses our service TelegramBot and the setWebhook method.
<?php
namespace App\Console\Commands;
use App\Services\TelegramBot;
use Illuminate\Console\Command;
class SetupTelegramWebhook extends Command
{
protected $signature = 'app:setup-telegram-webhook';
protected $description = 'Set Telegram webhook URL';
public function handle(TelegramBot $bot): int
{
$url = config('app.url') . '/telegram/webhook/' . config('services.telegram.token');
$result = $bot->call('setWebhook', [
'url' => $url,
'drop_pending_updates' => true, // чистим старые обновления при деплое
'allowed_updates' => ['message', 'callback_query'], // только нужные типы
]);
// Результат setWebhook — просто true при успехе
$this->info("Webhook set: " . ($result === true ? 'ok' : 'failed'));
// Проверить текущий вебхук можно так:
$info = $bot->call('getWebhookInfo');
$this->line('Current webhook: ' . json_encode($info, JSON_PRETTY_PRINT));
return self::SUCCESS;
}
}
Run: php artisan app:setup-telegram-webhook. In the output, you will see JSON from getWebhookInfo — check the fields url, pending_update_count, last_error_date/last_error_message.
Using the Service in the Application (Jobs, Listeners, Controllers)
The service is automatically resolved via the container. To send notifications from background tasks, create a Job with implements ShouldQueue — Laravel will handle retries automatically on exceptions from TelegramBot::call().
<?php
namespace App\Jobs;
use App\Services\TelegramBot;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class NotifyUser implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
protected int|string $chatId,
protected string $message
) {}
public function handle(TelegramBot $bot): void
{
$bot->sendMessage($this->chatId, $this->message);
}
}
// Диспатч из любого места:
// NotifyUser::dispatch(123456789, 'Ваш заказ #123 принят!');
Configure retries in config/queue.php (retry_after) and run the worker with the --tries=3 flag. On a network error or ok=false, the Job will fail to failed_jobs after exhausting attempts.
Debugging: How to Inspect What Telegram Sent
During development, it is convenient to log the entire update. Add to the top of the controller:
<?php
// ... внутри __invoke
\Log::channel('telegram')->info('Incoming update', [
'update_id' => $update['update_id'] ?? null,
'payload' => $update,
]);
In config/logging.php, add a channel:
// config/logging.php
'channels' => [
// ...
'telegram' => [
'driver' => 'daily',
'path' => storage_path('logs/telegram.log'),
'level' => 'info',
'days' => 14,
],
],
Now tail -f storage/logs/telegram-*.log will show the structure of each update — useful for understanding fields like message, callback_query, inline_query, etc.
Next: БД и состояния (FSM)