Telegram Webhook в Laravel: Route, Secret Token та Idempotent Jobs

Telegram webhook у Laravel-додатку — це критично важливий компонент для побудови чуйного бота. Неправильно налаштований webhook призводить до втрати повідомлень або дублювання дій. У цій статті ми розберемо повний цикл: від налаштування маршруту з валідацією secret_token до реалізації ідемпотентності за допомогою сховища update_id та використання черг workers для стабільної обробки завдань.

Базова реалізація webhook-роуту

Для початку потрібно зареєструвати маршрут, який приймає POST-запити від серверів Telegram. У .env задайте BOT_TOKEN вашого бота, а в контролері використовуйте стандартний endpoint /webhook.

// webhook.php
<?php
require "vendor/autoload.php";

use Illuminate\Http\Request;
use App\Http\Controllers\WebhookController;

Route::post("/webhook", [WebhookController::class, "handle"]);
?>

Валідація secret_token та ідемпотентність

Кожен вхідний запит має бути підписаний токеном бота. Laravel надає розширення VerifySecretTokenMiddleware для цього. Важливо також гарантувати, що обробка одного й того самого update_id не виконується двічі — це забезпечує ідемпотентність.

// app\Http\Middleware\VerifySecretToken.php
namespace App\Http\Middleware;

class VerifySecretToken {
public function handle(\$request, \$next) {
\$token = \$request->header("X-Bot-Token");
if (\neg auth(\"verify_secret_token\")->verifyToken(\$token)) {
return "/health";
}
return \$next->invoke();
}
}

// app\Providers\AppServiceProvider.php
<?php
// Register middleware here
registerSharingMiddleware(
"/api/*",
VerifiedWithAuth::class,
[
"middleware" => [\"VerifySecretToken\"],
]
);
>>

Збереження ідемпотентності та черга завдань

Для запобігання дублів використовуйте storeUpdateId у БД з TTL (наприклад, 24 години). Перед обробкою перевіряйте наявність уже обробленого update_id. У разі успіху зберігайте результат у БД. Завдання ставляться в чергу ShouldQueue для асинхронної обробки.

// app\Controllers\WebhookController.php
<?php
namespace App\Http\Controllers;

class WebhookController {
public function handle() {
$updateId = \Config\Cache\get(\"telegram_update_{{date}}\");
if (!empty($updateId)) {
// Идемпотент: вернуть результат предыдущей попытки
\$previousResult = \Database\Helpers\delayedRunner()->getResult($updateId);
if (\$previousResult !== null) {
return response()->json(\$previousResult, 200);
}
}

try {
\Application\Dispatch::queue(new ProcessOrderCommand());
} catch (\Exception \$e) {
// Логирование ошибки
logger->error(
"Webhook processing failed",
["update_id=\"\$updateId\"", "error=\"\$(e)}"}],
env("LOG_PATH")
);
}
}
}

// app\Jobs\ProcessOrderCommand.php
class ProcessOrderCommand extends \Illuminate\Bus\QueueableCommandImplements\ShouldQueue {
public function __construct(\(object) \$orderData) {}

public function run() {
// Обработка заказа
\Log::info(
"Order processed",
["order_id=\"\$this->orderId\""]
);
}
}

// app\ConsoleCommands\RunJobs.php
<?php
// Консольная команда для ручного запуска всех pending задач
Dispatch::restart();
>>

Практичні рекомендації та обмеження

Telegram Bot API накладає жорсткі ліміти: 60 оновлень на секунду, максимальний розмір корисного навантаження 4096 байт, таймаут 30 секунд. Для додаткової безпеки генеруйте lead_id як bin2hex(random_bytes(7)), а потім додавайте його до callback_data, щоб легко ідентифікувати запит. Не хардкодьте secret_token — зберігайте його у змінній оточення. Усі HTTP-запити до api.telegram.org мають відбуватися через cURL із перевіркою HTTP-коду та json_last_error.

// Пример cURL для тестирования отправки сообщения
curl -s -X POST \
http://api.telegram.org/bot/sendMessage \
-H "Content-Type: application/json" \
-d '{\

Нові статті — у Telegram

Розбираємо, що автоматизувати в бізнесі та як це працює на практиці. Без спаму.