Мы занимаемся веб-разработкой, CRM-системами и интеграциями. В одном из текущих проектов имеем CRM, где Telegram используется не просто для уведомлений, а как один из каналов взаимодействия с пользователем.
Схема совершенно стандартная:
Пользователь подписывается и пишет Telegram-боту → Telegram Webhook → Наш сервер → PHP логика → Ответ пользователю через Telegram Bot API
Ничего экзотического.
И вот в какой-то момент эта схема перестала работать.
Причём перестала весьма интересно: Telegram прекрасно принимал наши исходящие обращения через API, бот мог отправлять сообщения пользователю, но в обратную сторону — Telegram → наш webhook — запросы практически перестали доходить.
getWebhookInfo при этом сообщал:
Connection timed out
Так начались два дня поисков бага, которого, как оказалось, в нашем коде вообще не было.
Первое подозрение — конечно же, собственный код
Когда перестаёт работать webhook, первым делом думаешь на приложение.
Проверили обработчик.
Отправляем обычный POST:
curl -X POST \ -H "Content-Type: application/json" \ -d '{"test":"hello"}' \ https://example.ru/telegram/webhook.php
Запрос приходит.
PHP получает:
{ "test": "hello" }
Логи пишутся, обработчик работает.
Проверили права, конфигурацию PHP, веб-сервер, SSL, DNS.
Всё нормально.
Но Telegram продолжает говорить:
Connection timed out
Причём в логах веб-сервера в момент отправки сообщения боту вообще ничего нет.
Это важный момент.
Если бы запрос дошёл до nginx/Apache и там получил 403, 404, 500 или ещё что-нибудь — было бы понятно, куда копать.
Но запрос просто отсутствовал.
Может быть, проблема в хостинге?
Сервер находился на популярном shared хостинге.
Мы написали в техническую поддержку, подробно описали ситуацию.
Ответ был примерно ожидаемый: со стороны хостинга ограничений нет, сервер доступен, сетевых ограничений для нашего аккаунта не установлено.
Кроме того, поддержка обратила наше внимание на происходящие в России ограничения работы Telegram.
Но предположение — это ещё не диагностика.
Поэтому решили проверить сами.
Первоначально webhook работал на популярном в РФ shared хостинге.
Мы перенесли тестовый обработчик на отдельный VPS у того же популярного в РФ провайдера.
Получилась одинаковая картина в обоих случаях:
Telegram ↓ Популярный shared хостинг ↓ Connection timed out ❌
Оба наших эксперимента всё ещё находились внутри инфраструктуры одного провайдера.
Нужна была независимая точка.
Эксперимент расставил всё по местам
Нам нужно было окончательно отделить проблему Telegram от проблемы нашего сервера.
Для этого мы направили webhook на независимый внешний endpoint, который позволяет увидеть входящие HTTP-запросы.
Меняем URL Telegram webhook с нашего сервера на внешний endpoint.
Пишем боту: Привет
И запрос приходит практически мгновенно.
Возвращаем webhook обратно на наш сервер и снова получаем "Connection timed out"
Telegram → внешний endpoint ✅ мгновенно Telegram → наш сервер ❌ timeout
После этого стало понятно, что проблема находится не в самом Telegram webhook и не в нашем обработчике.
Получалась следующая картина:
Проверка | Результат |
Обычный HTTPS POST → наш сервер | ✅ |
PHP webhook | ✅ |
Наш сервер → Telegram Bot API | ✅ |
Telegram → наш сервер | ❌ timeout |
Telegram → независимый внешний endpoint | ✅ мгновенно |
Telegram получает сообщение от пользователя, формирует update и способен доставить его на внешний сервер. Но при попытке доставить тот же update непосредственно на наш сервер соединение не устанавливается.
Причём запрос не появляется даже в логах веб-сервера.
Следовательно, искать проблему внутри PHP уже не имело смысла.
Изящное решение
Вместо того чтобы пытаться выяснить, где именно и каким образом обрывается соединение Telegram → наш сервер, мы решили просто убрать проблемный участок из цепочки.
На этом этапе у нас был достаточно чистый эксперимент. Мы могли независимо проверить каждую сторону соединения: наш сервер принимал обычные POST-запросы, Telegram успешно доставлял webhook на внешний endpoint, а прямое соединение Telegram → наш сервер завершалось timeout. Поэтому вместо дальнейшего поиска ошибки в приложении мы решили изменить сам маршрут доставки webhook.
Для этого мы использовали Cloudflare Workers. Смысл решения оказался предельно простым: Telegram отправляет webhook не непосредственно на наш сервер, а на небольшой Worker в инфраструктуре Cloudflare. Worker получает JSON от Telegram и тут же пересылает его обычным HTTPS-запросом на наш сервер.
Получилась такая схема:
Как это настроили
В Cloudflare создаём аккаунт и открываем:
Workers & Pages → Create application → Create Worker
Создаём Worker, например:
telegram-webhook
После создания Cloudflare выдаёт ему собственный адрес вида:
https://telegram-webhook-xxxx.workers.dev
Никаких изменений DNS нашего основного сайта при этом не требуется.
Дальше открываем Edit code и заменяем стандартный код Worker на небольшой прокси:
export default { async fetch(request, env) { if (request.method !== "POST") { return new Response("OK"); } const body = await request.text(); const response = await fetch( "https://наш-сайт.ru/telegram/webhook.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: body } ); if (!response.ok) { return new Response("Upstream error", { status: 502 }); } return new Response("OK", { status: 200 }); } };
Нажимаем Deploy.
После этого меняем URL webhook Telegram: вместо нашего PHP-скрипта указываем адрес Cloudflare Worker:
https://telegram-webhook-xxxx.workers.dev
И всё.
Telegram теперь обращается к Cloudflare, а Cloudflare уже сам обращается к нашему серверу.
Для production-варианта, конечно, стоит добавить проверку secret_token Telegram и вынести адрес backend и секреты в переменные окружения Worker. Но для проверки самой гипотезы нам хватило буквально нескольких строк.
И тут получилось довольно забавно
Cloudflare в России заблокирован, но, никто не запрещал самому Cloudflare обращаться к нашим российским сайтам.
Поэтому получилась немного парадоксальная схема: Telegram не может нормально доставить webhook непосредственно на наш сервер, зато может доставить его Cloudflare, а Cloudflare уже спокойно передаёт его дальше.

