Skip to main content
Один токен бота Telegram выполняет в Omniflow четыре работы. Он ведёт клиентского бота — основную поверхность продаж и самообслуживания. Он проводит платежи Telegram Stars, которые приходят в собственный аутентифицированный поток обновлений бота, а не на какой-либо публичный маршрут. Он проверяет полезную нагрузку Login Widget и Mini App, по которой клиент входит в веб-панель. И он доставляет операторские уведомления в форумную группу, которой вы управляете. Эти четыре применения распределены по трём процессам, и все читают один и тот же APP_TELEGRAM_TOKEN из одного и того же .env: процесс бота ведёт чат, API проверяет входы и создаёт счета Stars, а воркер списывает продления через адаптер Stars. Раздельные токены позволили бы им разойтись во мнении о том, с каким ботом разговаривает клиент. Эта страница — про настройку. Что бот на самом деле делает для клиента — каждая команда, экран и сценарий — описано в Telegram-боте.

Создание бота

1

Поговорите с BotFather

Откройте @BotFather, отправьте /newbot и ответьте отображаемым именем и именем пользователя, оканчивающимся на bot. BotFather пришлёт токен.
2

Положите токен в .env

Пустой токен — не ошибка: процесс бота пишет в лог telegram bot disabled because APP_TELEGRAM_TOKEN is empty и простаивает. Это сделано намеренно — установке, которая продаёт только через веб-панель, бот для запуска не нужен.
3

Запустите стек и посмотрите лог

Бот пишет telegram bot started с выбранным режимом обновлений. При старте он также вызывает getMe, чтобы узнать собственное имя пользователя, — именно это делает возможными реферальные ссылки и веб-виджет Login Widget. Если этот вызов не удался — Telegram ненадолго недоступен во время старта контейнера, — лог сообщит об этом, а бот спросит снова, когда имя понадобится экрану рефералов: не раньше чем через 30 секунд, затем с удваивающейся паузой до одного раза в час, так что перезапуск не нужен.
4

Привяжите веб-домен, если вы запускаете клиентскую панель

Отправьте BotFather /setdomain и укажите хост, с которого раздаётся ваша клиентская панель. Telegram отрисовывает Login Widget только на домене, привязанном к боту. Без этого экран входа сообщает telegram: false и не показывает кнопку Telegram — честно, но не то, чего вы хотели.
Настраивать меню команд не нужно. Бот при каждом старте публикует собственный список через setMyCommands, на английском и русском: /start, /menu, /plans, /orders, /wallet, /settings, /support, /login и /cancel. Правка списка в BotFather будет перезаписана при следующем перезапуске. /ops намеренно отсутствует в опубликованном списке — см. Операторские инструменты.

Длинные опросы или вебхук

Длинные опросы — режим по умолчанию, и он остаётся поддерживаемым как явный режим разработки и запасной вариант. Бот выбирает режим по тому, задан ли URL вебхука; третьей настройки, которую пришлось бы держать в согласии, нет.

Включение вебхука

Оба значения проверяются при старте, и неверное значение фатально, а не скрыто:
  • APP_TELEGRAM_WEBHOOK_URL должен разбираться и использовать https, иначе бот завершается с APP_TELEGRAM_WEBHOOK_URL must be an HTTPS URL.
  • APP_TELEGRAM_WEBHOOK_SECRET должен соответствовать ^[A-Za-z0-9_-]{32,256}$ — набору символов, который принимает сам Telegram, — иначе бот завершается с сообщением, называющим правило.
При старте бот вызывает setWebhook с URL, секретным токеном и явным списком allowed_updates из message, edited_message, callback_query, pre_checkout_query, my_chat_member и chat_member. Сбой регистрации фатален: бот, который молча продолжал бы опрашивать, пока оператор считает, что он на вебхуке, был бы хуже, чем бот, отказавшийся стартовать. Перечисление типов обновлений вместо приёма всего подряд означает, что Telegram не доставляет виды обновлений, которые никто не обрабатывает. Секретный токен — это вся история аутентификации для этого маршрута. Telegram присылает его в заголовке при каждой доставке, и библиотека проверяет его до разбора обновления, поэтому запрос без него не доходит ни до одного обработчика. Эндпоинт также ограничивает тело 1 MiB. Направьте обратный прокси на APP_BOT_HTTP_ADDR и терминируйте TLS там; см. обратный прокси.
Хост вебхука доступен из интернета, поэтому бот отказывается публиковать там собственную диагностику. APP_BOT_METRICS_ADDR отдаёт /livez, /readyz, /metrics и /healthz, и он игнорируется, когда равен APP_BOT_HTTP_ADDR. Задайте два разных адреса и выставляйте наружу только вебхуковый. Пустой APP_BOT_METRICS_ADDR в режиме длинных опросов полностью оставляет бота без HTTP.

Возврат назад

Очистите APP_TELEGRAM_WEBHOOK_URL и перезапустите. Telegram хранит ранее зарегистрированный вебхук, пока его что-нибудь не удалит, поэтому удалите его ещё и через Bot API (deleteWebhook), иначе бот будет опрашивать и по-прежнему ничего не получать.

Mini App и Login Widget

Клиентская веб-панель входит от имени клиента из Telegram двумя способами, и оба проверяют полезную нагрузку, а не доверяют ей. Login Widget — это кнопка на /account/sign-in. Telegram подписывает её поля через HMAC-SHA256 с ключом SHA-256 от токена бота, поэтому полезная нагрузка, которая вообще проверяется, доказывает, что Telegram создал её для этого бота. Виджет загружается по имени пользователя бота, которое Omniflow получает из getMe при первом обращении и кэширует, — одной переменной окружения меньше, которую пришлось бы держать в согласии с токеном. Если имя пользователя получить не удалось, экран входа сообщает telegram: false и не показывает кнопку, вместо того чтобы рисовать ту, которая никогда не появится. Mini App входит вообще без кнопки. При открытии внутри Telegram окружающий клиент передаёт странице подписанную строку initData, которую страница отправляет в POST /v1/account/auth/telegram/miniapp. Проверка отличается ровно в одном существенном месте: ключ HMAC здесь — HMAC-SHA256("WebAppData", botToken), а не простой SHA-256 от токена. Эти два пути остаются раздельными в коде, а не делят один хелпер, который угадывал бы, что есть что, потому что ключ виджета здесь не проверил бы ничего. Обе проверки ограничивают auth_date одной минутой (с допуском в 30 секунд на спешащие часы клиента). Хеш сам по себе не истекает, поэтому без этого ограничения однажды перехваченная полезная нагрузка — из истории браузера, из реферера, со снимка экрана с URL — аутентифицировала бы вечно.
1

Задайте домен виджета

/setdomain в BotFather, с хостом, который отдаёт /account. Именно это заставляет Login Widget вообще отрисовываться.
2

Зарегистрируйте Mini App, если он вам нужен

Команда /newapp у BotFather привязывает URL веб-приложения к боту. Клиентской панели Omniflow ничего дополнительно не требуется: открытая внутри Telegram, она обнаруживает initData и входит автоматически, а открытая в обычном браузере — откатывается к виджету.
3

Проверьте

GET /v1/account/auth/methods сообщает, что эта установка предлагает на самом деле: {telegram, telegramBot, magicLink, oidc[]}. Экран входа отрисовывается ровно по этому, поэтому то, что вы там видите, — это то, что API считает настроенным.
Существует третий, включаемый оператором путь для установок, где виджет непригоден: бот присылает одноразовую ссылку для входа в ответ на /login. По умолчанию он выключен — APP_CUSTOMER_MAGIC_LINK_ENABLED=false — потому что это второй предъявительский credential, путешествующий через чат. Для включения нужны APP_PUBLIC_URL и, на боте, 32-байтовый APP_DATA_ENCRYPTION_KEY. Веб-панель описывает все три пути со стороны клиента.

Операторская группа

Операторские уведомления идут в одну группу Telegram с включёнными Топиками. Бот создаёт и привязывает по одному форумному топику на каждый вид события и пересоздаёт удалённый; от вас нужна только группа.
1

Создайте группу и включите Топики

Именно группу, а не канал. Включите Топики в настройках группы — бот не может создать форумный топик в группе, где их нет.
2

Добавьте бота и выдайте право Manage topics

Повысьте бота до администратора как минимум с правом Manage topics. Без него создание топика падает и записывается как missing_manage_topics_permission.
3

Задайте идентификатор чата

Перезапустите бота. Если значение оставлено равным 0, нотификатор пишет в лог operator notifications are disabled because no group is configured, а постановка в очередь становится пустой операцией — уведомления даже не сохраняются, поэтому включение группы позже не проиграет историю заново.
4

Убедитесь

Следите за operator topic could not be created в логе бота или читайте operator_topics.status и last_error_code. Классифицированные коды: missing_manage_topics_permission, chat_not_found, forum_topics_disabled, bot_not_in_group и telegram_error — сырое сообщение Telegram отбрасывается, потому что оно может отражать содержимое запроса.
Всё, что получает группа, намеренно не содержит содержимого. Уведомление называет событие, класс суммы и идентификаторы, которые оператор может найти; оно никогда не несёт текста клиента, ссылки на подписку, токена или платёжной полезной нагрузки. Отрисовка использует явный список разрешённых полей, поэтому поле, добавленное кем-то в полезную нагрузку позже, игнорируется, а не публикуется по случайности. Объём ограничен: после APP_OPERATOR_NOTIFICATION_CAP сообщений одного вида за APP_OPERATOR_NOTIFICATION_WINDOW_MINUTES (по умолчанию 30 и 5) дальнейшие уведомления завершаются как suppressed с volume_cap, и в лог пишется предупреждение. Ключ дедупликации схлопывает повторные срабатывания по одному событию — повторно доставленный вебхук, второй проход сверки — в одно сообщение. Операторские уведомления описывают саму очередь.

Операторские инструменты в чате

Резервное копирование и восстановление — единственные административные действия, которые несёт Telegram, и только для явно названных аккаунтов. APP_OPERATOR_TELEGRAM_IDS — это список ID пользователей Telegram через запятую, которым разрешено выполнять /ops; пустое значение полностью отключает команду, а некорректная запись — фатальная ошибка конфигурации, а не молча отброшенный оператор. Команды нет в опубликованном списке команд, поэтому она не появляется в меню ни у кого. См. резервные копии и восстановление.

Обязательные каналы

Если вы ограничиваете доступ членством в канале, отвечает на этот вопрос именно бот, через getChatMember. Добавьте бота в каждый обязательный канал администратором, чтобы он мог читать членство. Участник в режиме без права голоса (restricted) всё равно считается участником, пока Telegram сообщает is_member, — ограниченный аккаунт, покинувший канал, участником не считается, — а ошибка трактуется как «не смог ответить», а не как «нет»: воркер записывает unknown и ждёт, а контроль при покупке пропускает продажу. Ни то, ни другое не гадает. Обязательные каналы описывают политику.

Справочник по конфигурации

Ничто не перечитывает окружение: изменение любой из этих переменных означает перезапуск процесса.
На /admin/settings есть раздел Telegram с именем пользователя бота, URL вебхука, переключателем включения вебхука и полями токена и секрета вебхука, доступными только на запись, а также раздел Operator group с идентификатором чата, переключателем топиков и ограничением уведомлений с окном. Оба сохраняют то, что вы вводите, запечатанным ключом установки, — и ни один работающий процесс не читает их обратно. Бот настраивается из окружения при старте. См. настройки установки.

О надёжности стоит знать

  • Значимые колбэки — подтверждение, отмена, выбор способа оплаты, счёт, удаление устройства, ротация ссылки — захватываются ровно один раз на каждый callback query в Valkey, а само нажатие (чат, сообщение, кнопка) захватывается на десять секунд, поэтому повторно доставленное обновление или двойное нажатие не могут повторить действие. Именно поэтому бот отказывается стартовать без APP_VALKEY_URL.
  • Сбои доставки классифицируются: ожидание из-за флуда, заблокировавший бота пользователь и удалённый аккаунт — разные проблемы. Заблокировавший или удалённый аккаунт перестаёт тратить повторы и исключается из дальнейшей доставки, пока не восстановится.
  • Каждое обновление несёт идентификатор корреляции, который логируется вместе с заказом, платежом, задачей и запросом к Remnawave, который оно вызвало, поэтому одна строка связывает сообщение клиента со всем, чего оно коснулось.
  • APP_METRICS_ENABLED не применяется к боту: он строит свой реестр метрик безусловно. То, что решает, доступны ли они, — это APP_BOT_METRICS_ADDR.