> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Telegram

> Создайте бота, выберите длинные опросы или вебхук, привяжите домен Mini App и настройте операторскую группу с топиками

Один токен бота Telegram выполняет в Omniflow четыре работы. Он ведёт клиентского бота — основную
поверхность продаж и самообслуживания. Он проводит платежи Telegram Stars, которые приходят в
собственный аутентифицированный поток обновлений бота, а не на какой-либо публичный маршрут. Он
проверяет полезную нагрузку Login Widget и Mini App, по которой клиент входит в веб-панель. И он
доставляет операторские уведомления в форумную группу, которой вы управляете.

Эти четыре применения распределены по трём процессам, и все читают один и тот же
`APP_TELEGRAM_TOKEN` из одного и того же `.env`: процесс бота ведёт чат, API проверяет входы и
создаёт счета Stars, а воркер списывает продления через адаптер Stars. Раздельные токены позволили
бы им разойтись во мнении о том, с каким ботом разговаривает клиент.

Эта страница — про настройку. Что бот на самом деле делает для клиента — каждая команда, экран и
сценарий — описано в [Telegram-боте](https://omniflow-vpn.mintlify.site/ru/customer/telegram-bot.md).

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

**1. Поговорите с BotFather**

Откройте [@BotFather](https://t.me/BotFather), отправьте `/newbot` и ответьте отображаемым
именем и именем пользователя, оканчивающимся на `bot`. BotFather пришлёт токен.

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

```env theme={null}
APP_TELEGRAM_TOKEN=123456789:AA…
```

Пустой токен — не ошибка: процесс бота пишет в лог
`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` | Заданным значением |
| Входящий порт | нет | `APP_BOT_HTTP_ADDR`, по умолчанию `:8082`, путь `POST /telegram/webhook` |
| Нужен публичный HTTPS-хост | нет | да |
| Задержка | интервал опроса | немедленно |
| Подходит для | локальной разработки, хоста без входящего пути | продакшена за обратным прокси |

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

```env theme={null}
APP_TELEGRAM_WEBHOOK_URL=https://bot.example.com/telegram/webhook
APP_TELEGRAM_WEBHOOK_SECRET=a-random-string-of-at-least-32-characters
APP_BOT_HTTP_ADDR=:8082
```

Оба значения проверяются при старте, и неверное значение фатально, а не скрыто:

* `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 там; см.
[обратный прокси](https://omniflow-vpn.mintlify.site/ru/operations/reverse-proxy.md).

> [!WARNING]
>
> Хост вебхука доступен из интернета, поэтому бот отказывается публиковать там собственную
> диагностику. `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`. [Веб-панель](https://omniflow-vpn.mintlify.site/ru/customer/web-panel.md) описывает все три пути со стороны
клиента.

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

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

| Топик | Вид |
| - | - |
| 💳 Покупки | `purchase` |
| ♻️ Продления | `renewal` |
| 👛 Пополнения кошелька | `topup` |
| ↩️ Возвраты | `refund` |
| ⚠️ Сбои исполнения | `fulfillment_failure` |
| 🚨 Инциденты | `incident` |
| 💾 Резервные копии | `backup` |
| 🔐 Админская безопасность | `security` |

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

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

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

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

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

```env theme={null}
APP_OPERATOR_CHAT_ID=-1001234567890
```

Перезапустите бота. Если значение оставлено равным `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`, и в лог пишется предупреждение. Ключ дедупликации
схлопывает повторные срабатывания по одному событию — повторно доставленный вебхук, второй проход
сверки — в одно сообщение. [Операторские уведомления](https://omniflow-vpn.mintlify.site/ru/operations/operator-notifications.md)
описывают саму очередь.

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

Резервное копирование и восстановление — единственные административные действия, которые несёт
Telegram, и только для явно названных аккаунтов. `APP_OPERATOR_TELEGRAM_IDS` — это список ID
пользователей Telegram через запятую, которым разрешено выполнять `/ops`; пустое значение
полностью отключает команду, а некорректная запись — фатальная ошибка конфигурации, а не молча
отброшенный оператор. Команды нет в опубликованном списке команд, поэтому она не появляется в
меню ни у кого. См. [резервные копии и восстановление](https://omniflow-vpn.mintlify.site/ru/operations/backup-restore.md).

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

Если вы ограничиваете доступ членством в канале, отвечает на этот вопрос именно бот, через
`getChatMember`. Добавьте бота в каждый обязательный канал администратором, чтобы он мог читать
членство. Участник в режиме без права голоса (`restricted`) всё равно считается участником, пока
Telegram сообщает `is_member`, — ограниченный аккаунт, покинувший канал, участником не считается, — а
ошибка трактуется как «не смог ответить», а не как «нет»: воркер записывает `unknown` и ждёт, а
контроль при покупке пропускает продажу. Ни то, ни другое не гадает.
[Обязательные каналы](https://omniflow-vpn.mintlify.site/ru/operations/required-channels.md) описывают политику.

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

| Переменная | По умолчанию | Назначение |
| - | - | - |
| `APP_TELEGRAM_TOKEN` | нет | Токен Bot API. Обязателен для бота; на API включает вход через Telegram и адаптер Stars, на воркере — адаптер Stars для продлений |
| `APP_TELEGRAM_WEBHOOK_URL` | пусто (длинные опросы) | Должен быть HTTPS. Заданное значение переключает бота в режим вебхука |
| `APP_TELEGRAM_WEBHOOK_SECRET` | нет | Обязателен вместе с URL. 32–256 символов из `A–Z a–z 0–9 _ -`; проверяется до разбора обновления |
| `APP_BOT_HTTP_ADDR` | `:8082` | Адрес слушателя вебхука |
| `APP_BOT_METRICS_ADDR` | пусто | Слушатель проб и метрик. Игнорируется, когда равен `APP_BOT_HTTP_ADDR` |
| `APP_VALKEY_URL` | нет | Обязателен для бота. Обеспечивает ограничения частоты запросов и защиту от повтора колбэков; без него бот отказывается стартовать |
| `APP_OPERATOR_CHAT_ID` | `0` | Операторская форумная группа. `0` отключает операторские уведомления |
| `APP_OPERATOR_TELEGRAM_IDS` | пусто | Аккаунты Telegram, которым разрешено выполнять `/ops`. Пустое значение отключает команду |
| `APP_OPERATOR_NOTIFICATION_CAP` | `30` | Сообщений на вид за окно, после чего остальные подавляются |
| `APP_OPERATOR_NOTIFICATION_WINDOW_MINUTES` | `5` | Длина этого окна |
| `APP_SUPPORT_URL` | нет | Необязательный контакт оператора, показываемый рядом с поддержкой в боте. Проверяется как http/https |
| `APP_PUBLIC_URL` | нет | База для возвратных ссылок провайдеров, реферальных ссылок и ссылок для входа. Обязателен с YooKassa и со ссылкой для входа |
| `APP_TERMS_URL` | нет | Условия обслуживания, на которые ссылаются экраны тарифов и оформления заказа |
| `APP_DEFAULT_CURRENCY` | `RUB` | Должна соответствовать `^[A-Z]{3}$`. Используйте `XTR` для установки только на Telegram Stars |
| `APP_MARKETING_FREQUENCY_CAP` | `3` | Маркетинговых сообщений за окно; `0` отключает ограничение |
| `APP_MARKETING_WINDOW_HOURS` | `168` | Длина маркетингового окна |
| `APP_RECOVERY_WINDOW_HOURS` | `336` | Сколько истёкшая подписка сохраняет предложение восстановления в одно нажатие |
| `APP_TRIAL_MINIMUM_ACCOUNT_AGE_HOURS` | `0` | Отказывать в пробном периоде аккаунтам Telegram моложе этого возраста |
| `APP_CART_TTL_HOURS` | `720` | Сколько сохранённая корзина ждёт, пока баланса на неё хватит. Читается только ботом |
| `APP_CUSTOMER_MAGIC_LINK_ENABLED` | `false` | Включает `/login` и доставляемую ботом ссылку для входа в веб |

Ничто не перечитывает окружение: изменение любой из этих переменных означает перезапуск процесса.

> [!WARNING]
>
> На `/admin/settings` есть раздел **Telegram** с именем пользователя бота, URL вебхука,
> переключателем включения вебхука и полями токена и секрета вебхука, доступными только на
> запись, а также раздел **Operator group** с идентификатором чата, переключателем топиков и
> ограничением уведомлений с окном. Оба сохраняют то, что вы вводите, запечатанным ключом
> установки, — и **ни один работающий процесс не читает их обратно**. Бот настраивается из
> окружения при старте. См. [настройки установки](https://omniflow-vpn.mintlify.site/ru/operations/installation-settings.md).

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

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.