Webhook is the main way to get updates Telegram-бот and in production: faster than long polling, does not keep a constant connection and normally lives behind the load balancer. In Yii2, receiving updates is done in one action, but it is in it that three things most often break: CSRF validation, reprocessing the same update, and timeouts on heavy logic. Let's disassemble the working ligament.
1. Webhook registration and environment variables
First, fix the token and the secret in params.php or in ENV. No literals in the code:
Generate a secret once and store next to the token. Installing webhook:
allowed_updates restricts flow: fewer types — less noise. After changing the URL or secret setWebhook is called again, otherwise Telegram will continue to send updates to the old address.
2. CSRF routing and shutdown for bot routing
The webhook endpoint must be POST and must not pass through the standard Yii2 CSRF filter, otherwise Yii simply will not allow the request, because Telegram does not have a CSRF token. The correct path is to disable CSRF pointwise for a specific action, not globally:
Globally enableCsrfValidation = false you do not need to set it — this will turn off the protection of the forms of the entire site.
3. X-Telegram-Bot-Api-Secret-Token check
Telegram sends a header X-Telegram-Bot-Api-Secret-Token with each update, if you asked User Secret Token in setWebhook. Compare strictly and consistently over time, otherwise anyone will be able to jiggle your URL:
hash_equals — required: normal === theoretically vulnerable to time-based attacks on short secrets.
4. Idempotency by update_id
Telegram promises that update_id unique and monotonously growing, and will deliver the same update_id. Without double protection, you will double debit money, send two identical messages, and generate leads. A table is made processed_updates:
There is no need to store "one global cursor" — PK for update_id solves the idempotency problem more reliably. Periodically clean old records, for example, once a day, delete everything older than 7 days.
5. Queue: Yii Queue for heavy processing
Telegram is waiting for a response on webhook no longer than ~60 seconds, and keeps the connection all the time. If you write to the database in action, send HTTP to CRM, calculate analytics and respond to the user, it is easy to catch a timeout and retry. Solution: in action, only confirm the appointment, and put the processing in the queue.
Post in processed_updates and the push to the queue in one transaction is a protection against loss: if the queue fell along with the commit, the worker will pick up the entry; if the commit did not pass, the update will come again from Telegram.
6. Client to Bot API: cURL, check ok:false
A separate conversation — how to correctly pull from Yii2 api.telegram.org. file_get_contents hTTP stream prevents normal control of the timeout and HTTP code, and Telegram sometimes returns 5xx without a body. Client template:
Do not forget about the limits: no more than ~30 messages per second per bot in total and no more than 1 message per second in the same chat. On mass mailings, put delays and process 429 the scalp. retry_after.
7. Pre-launch checklist
- CSRF off only for action Webhook, all other forms are protected.
- The secret is set in setWebhook and compared in
hash_equals. - In action, it is first fixed update_id, then the job is put in the queue, then returns 200 OK.
- The Telegram response takes ≤ 1 second, all heavy logic is in the Yii Queue worker.
- Uses cURL, validates HTTP code and
ok = false;in JSON. - Tokens and secret — only in
params/ ENV, not in the repository.
Such a bundle survives Telegram retreats, long CRM calls and random duplicates, and most importantly — does not turn action into a thin place of the entire application.
If you need a ready-made webhook controller framework on Yii2 with a queue and an admin panel for the bot, see BotCreator.