Webhook — это основной способ получать обновления Telegram-бота в продакшене: быстрее long polling, не держит постоянное соединение и нормально живёт за балансировщиком. В Yii2 приём апдейтов делается в одном action, но именно в нём чаще всего ломаются три вещи: CSRF-валидация, повторная обработка одного и того же update и таймауты на тяжёлой логике. Разберём рабочую связку.
1. Регистрация webhook и переменные окружения
Сначала зафиксируем токен и секрет в params.php или в ENV. Никаких литералов в коде:
Секрет генерируйте один раз и храните рядом с токеном. Установка webhook:
allowed_updates ограничивает поток: меньше типов — меньше шума. После смены URL или секрета setWebhook вызывается заново, иначе Telegram продолжит слать апдейты на старый адрес.
2. Роутинг и отключение CSRF для bot-роута
Webhook-эндпоинт должен быть POST и не должен проходить через стандартный CSRF-фильтр Yii2, иначе Yii просто не пустит запрос, потому что у Telegram нет CSRF-токена. Правильный путь — отключить CSRF точечно для конкретного action, а не глобально:
Глобально enableCsrfValidation = false ставить не нужно — это выключит защиту форм всего сайта.
3. Проверка X-Telegram-Bot-Api-Secret-Token
Telegram присылает заголовок X-Telegram-Bot-Api-Secret-Token с каждым апдейтом, если вы задали secret_token в setWebhook. Сравнивайте строго и константно по времени, иначе кто угодно сможет дёргать ваш URL:
hash_equals — обязательно: обычный === теоретически уязвим к атакам по времени на коротких секретах.
4. Идемпотентность по update_id
Telegram обещает, что update_id уникален и монотонно растёт, и при ретрае доставит один и тот же update_id. Без защиты от дубля вы будете дважды списывать деньги, отправлять два одинаковых сообщения и плодить лиды. Делается таблица processed_updates:
Хранить «один глобальный курсор» не нужно — PK по update_id решает задачу идемпотентности надёжнее. Периодически чистите старые записи, например раз в сутки джобой удаляйте всё старше 7 дней.
5. Очередь: Yii Queue для тяжёлой обработки
Telegram ждёт ответа на webhook не дольше ~60 секунд, и всё это время держит соединение. Если вы в action пишете в БД, отправляете HTTP в CRM, считаете аналитику и отвечаете пользователю — легко словить таймаут и ретрай. Решение: в action только подтвердить приём, а обработку положить в очередь.
Запись в processed_updates и пуш в очередь в одной транзакции — это защита от потери: если очередь упала вместе с коммитом, воркер подхватит запись; если коммит не прошёл — апдейт придёт снова от Telegram.
6. Клиент к Bot API: cURL, проверка ok:false
Отдельный разговор — как из Yii2 корректно дёргать api.telegram.org. file_get_contents с потоком http не даёт нормально контролировать таймаут и HTTP-код, а у Telegram иногда возвращается 5xx без тела. Шаблон клиента:
Не забывайте про лимиты: не более ~30 сообщений в секунду на один бот суммарно и не более 1 сообщения в секунду в один и тот же чат. На массовые рассылки ставьте задержки и обрабатывайте 429 с retry_after.
7. Чек-лист перед боевым запуском
- CSRF выключен только для action webhook, остальные формы защищены.
- Секрет задан в setWebhook и сравнивается через
hash_equals. - В action сначала фиксируется update_id, потом кладётся джоб в очередь, потом возвращается 200 OK.
- Ответ Telegram уходит ≤ 1 секунды, вся тяжёлая логика — в воркере Yii Queue.
- Используется cURL, проверяется HTTP-код и
ok:falseв JSON. - Токены и секрет — только в
params/ ENV, не в репозитории.
Такая связка переживает ретраи Telegram, длинные CRM-вызовы и случайные дубли, а главное — не превращает action в тонкое место всего приложения.
Если нужен готовый каркас webhook-контроллера на Yii2 с очередью и админкой для бота — посмотрите BotCreator.