Контролер Webhook для Telegram-бота на Yii2: secret_token, idempotence та queue

Webhook — це основний спосіб отримання оновлень Telegram-бот та у виробництві: швидше, ніж довге опитування, не підтримує постійного зв 'язку та зазвичай живе за балансувальником навантаження. У Yii2 отримання оновлень здійснюється в одній дії, але саме в ній найчастіше ламаються три речі: перевірка CSRF, повторна обробка одного і того ж оновлення та тайм-аути на важку логіку. Давайте розберемо робочу зв' язку.

1. Реєстрація вебхука та змінні середовища

Спочатку зафіксуйте токен і секрет у params.php або в ENV. Немає літералів в коді:

Створіть секрет один раз і зберігайте поруч з маркером. Встановлення вебхука:

allowed_updates обмежує потік: менше типів — менше шуму. Після зміни URL-адреси або секрету setWebhook викликається знову, інакше Telegram продовжить надсилати оновлення на стару адресу.

2. Маршрутизація CSRF та вимкнення для маршрутизації ботів

Кінцева точка вебхука повинна бути POST і не повинна проходити через стандартний фільтр Yii2 CSRF, інакше Yii просто не дозволить запит, оскільки Telegram не має токена CSRF. Правильний шлях — вимкнути CSRF точково для певної дії, а не глобально:

Глобальний обсяг 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 для важкої обробки

Telegram чекає відповіді на вебхук не більше ~60 секунд і постійно підтримує зв 'язок. Якщо ви пишете в базу даних в дії, надсилаєте HTTP в CRM, розраховуєте аналітику і відповідаєте користувачеві, легко спіймати тайм-аут і повторити спробу. Рішення: у дії лише підтвердьте зустріч і поставте обробку в чергу.

Запис у processed_Updates а поштовх до черги в одній транзакції — це захист від втрати: якщо черга випала разом з фіксацією, працівник забере запис; якщо фіксація не пройшла, оновлення прийде знову з Telegram.

6. Client to Bot API: cURL, check ok:false

Окрема розмова — як правильно витягнути з Yii2 api.telegram.org. file_get_contents потік http перешкоджає нормальному контролю тайм-ауту та HTTP-коду, а Telegram іноді повертає 5xx без тіла. Шаблон клієнта:

Не забувайте про обмеження: не більше ~30 повідомлень в секунду на одного бота в цілому і не більше 1 повідомлення в секунду в одному чаті. На масових розсилках ставити затримки та обробляти 429 з retry_after.

7. Контрольний список перед запуском

  • CSRF вимкнено тільки для дій webhook, всі інші форми захищені.
  • Секрет встановлено в setWebhook і порівняли в hash_equals.
  • У дії спочатку закріплюється update_id, потім завдання ставиться в чергу, потім повертається 200 OK.
  • Відповідь Telegram займає ≤ 1 секунди, вся важка логіка знаходиться в Yii Queue worker.
  • Використовує cURL, перевіряє HTTP-код і ok:false в JSON.
  • Токени та секрет — лише в "params": { / ENV, а не в сховищі.

Такий бандл переживає Telegram-ретрити, довгі CRM-дзвінки та випадкові дублікати, а головне — не перетворює дію на тонке місце всієї програми.

Якщо вам потрібен готовий фреймворк контролера веб-перехоплювача на Yii2 з чергою та панеллю адміністратора для бота, див. BotCreator.

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

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