> ## 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, топики, которые бот создаёт сам, то, что эти сообщения сознательно опускают, и лимиты, которые сохраняют их читаемыми

От оператора требуется одно: группа в Telegram. Бот сам создаёт и привязывает
каждый топик внутри неё, поддерживает привязку и заново создаёт удалённый топик.

Поток сознательно сделан сигналом, а не хранилищем данных. Уведомление называет
событие, класс суммы и идентификаторы, которые оператор может посмотреть через
административный API, — и никогда текст клиента, ссылку на подписку, токен или
полезную нагрузку платежа. Группа в Telegram — это место, откуда сообщения
пересылают, откуда снимают скриншоты и где их хранят годами, поэтому вопрос при
проектировании звучал не «что было бы здесь удобно», а «что безопасно оставить в
чате навсегда».

## Настройка

**1. Создайте группу**

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

**2. Добавьте бота**

Добавьте бота и выдайте ему права **Управление топиками** и **Отправка сообщений**.

**3. Укажите боту группу**

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

Идентификатор супергруппы отрицателен и начинается с `-100`. Обычный способ его
узнать — переслать сообщение из группы боту вроде `@userinfobot`.

**4. Перезапустите бота**

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

Доставляет уведомления только процесс `bot`. Процессы `api` и `worker` пишут их в
очередь, поэтому установка без запущенного бота всё равно накапливает запись о
происходившем — ей просто некому об этом сообщить.

## Топики

| Топик | Вид | Что несёт | Кто пишет |
| - | - | - | - |
| 💳 Purchases | `purchase` | Оплаченные заказы подписок | Расчёт по заказу |
| ♻️ Renewals | `renewal` | Оплаченные продления и возобновления | Расчёт по заказу |
| 👛 Wallet top-ups | `topup` | Зачисленные пополнения, включая переплаты и недоплаты | Расчёт по пополнению |
| ↩️ Refunds | `refund` | Зарегистрированные возвраты | Регистрация возврата |
| ⚠️ Fulfillment failures | `fulfillment_failure` | Прогоны выдачи доступа, переставшие быть случайным сбоем, неудачные доставки цифровых товаров и дополнения, оплаченные при отсутствии действующего права на услугу | Воркер выдачи доступа, воркер товаров, расчёт |
| 🚨 Incidents | `incident` | Платежи, на которые надо взглянуть: дубликат, недоплата или переплата, несовпадение валюты или нехватка кошелька, оставившие деньги у провайдера при открытом заказе, платёж, проведший заказ, который клиент отменил, а также каждый переход режима обслуживания — автоматический или ручной | Проведение платежей, контроллер режима обслуживания в api |
| 💾 Backups | `backup` | Результаты резервного копирования — успех с размером или сбой с кодом ошибки — и результаты восстановления | Процесс, который снял или восстановил копию |
| 🔐 Admin security | `security` | Входы операторов с нового адреса, смены и сбросы паролей, отключение двухфакторной аутентификации и выдачи роли владельца | Сервис идентификации операторов |
| 💬 Support tickets | `support` | Каждый новый тикет поддержки — из бота или веб-кабинета — в виде идентификаторов тикета и очереди; никогда не тема и не сообщение, потому что это текст клиента | Триггер базы данных на `support_tickets` |
| 📣 Campaign previews | `campaign_test` | Одна копия сообщения рассылки, запрошенная до того, как аудитория утверждена | Тестовая отправка рассылки |
| 🔔 Notice previews | `notice_test` | Одна готовая копия транзакционного уведомления, которое переписывает оператор | Тестовая отправка уведомления |

Последние два несут то, чего остальные намеренно не несут, — готовое тело сообщения. Всё прочее здесь — набор разрешённых обезличенных полей, а предпросмотр по определению является самим текстом. Оба явно сообщают, что больше их никто не получил: предпросмотр, принятый за настоящую отправку, — это чья-то рука на кнопке паузы. См. [Кампании](https://omniflow-vpn.mintlify.site/ru/commerce/campaigns.md) и [Тексты сообщений](https://omniflow-vpn.mintlify.site/ru/operations/message-wording.md).

Топик инцидентов сегодня несёт платёжные аномалии и ничего больше. Каждое уведомление
называет заказ, провайдера, операцию, классификацию, валюту и сумму, а также состояние
заказа на тот момент и дедуплицируется по платёжному намерению и классификации —
повторно присланный вебхук ничего не добавляет. Что означает каждая классификация и как
она разрешается, описано в
[Заказах и платежах](https://omniflow-vpn.mintlify.site/ru/commerce/orders-payments.md#когда-платёж-не-проводится).
Режим обслуживания определяется в процессе `api`, у которого нет клиента Telegram, поэтому
api записывает каждый переход в ту же очередь `operator_notifications`, которую разбирает
бот; один переход — одно сообщение, с дедупликацией по направлению и минуте.

Результаты резервного копирования и восстановления так же ставит в очередь процесс,
который их выполнил, с ключом по копии и результату, поэтому повторённая задача сообщает
один раз. Записью остаётся таблица `backups`: уведомление, которое не удалось поставить в
очередь, попадает в лог и никогда не валит само копирование. См. [Режим обслуживания](https://omniflow-vpn.mintlify.site/ru/operations/maintenance-mode.md) и
[Резервные копии и восстановление](https://omniflow-vpn.mintlify.site/ru/operations/backup-restore.md).

> [!NOTE]
>
> Оценщик аномалий пишет уведомления под девятым видом, `anomaly`, который база
> данных принимает, но для которого у бота нет именованного топика. Эти уведомления
> до группы не доходят. Разбирайте аномалии на `/admin/risk` — см.
> [Контроль рисков](https://omniflow-vpn.mintlify.site/ru/operations/risk-controls.md).

## Конфигурация

| Переменная | По умолчанию | Назначение |
| - | - | - |
| `APP_OPERATOR_CHAT_ID` | `0` | Группа. `0` полностью отключает доставку. |
| `APP_OPERATOR_NOTIFICATION_CAP` | `30` | Сколько сообщений отправляется на каждый вид за окно, прежде чем остальные подавляются. `0` снимает лимит. |
| `APP_OPERATOR_NOTIFICATION_WINDOW_MINUTES` | `5` | Скользящее окно, по которому считается лимит. Нулевое или неразбираемое значение откатывается к пяти минутам. |

Все три читаются один раз при старте. Раздел `operator_group` на `/admin/settings/integrations`
хранит те же четыре числа и ничего не меняет — см.
[Настройки установки](https://omniflow-vpn.mintlify.site/ru/operations/installation-settings.md).

## Когда привязка не удаётся

Топик, который не удалось создать, записывается в `operator_topics` со статусом
`failed` и классифицированной причиной, а не молча остаётся без действия. Исходное
сообщение Telegram отбрасывается, потому что оно может отражать содержимое запроса.

| `last_error_code` | Что делать |
| - | - |
| `missing_manage_topics_permission` | Выдайте боту право «Управление топиками» |
| `forum_topics_disabled` | Включите топики в настройках группы |
| `chat_not_found` | Неверный идентификатор чата, либо группа была повышена до супергруппы и её идентификатор изменился |
| `bot_not_in_group` | Добавьте бота заново |
| `telegram_error` | Всё прочее; читайте лог бота, чтобы понять контекст |

Устраните причину и перезапустите бота или дождитесь следующего уведомления этого
вида: доставка перед отправкой заново пытается выполнить привязку.

Изменение `APP_OPERATOR_CHAT_ID` перепривязывает всё. У строк, чей сохранённый чат
больше не совпадает, идентификатор топика очищается, а статус сбрасывается в
`pending`, поэтому в новой группе создаются свежие топики, а не отправляются
сообщения в группу, которую вы покинули.

Топик, удалённый в Telegram, заново создаётся при следующем сообщении в него, один
раз, после чего сообщение повторяется.

## Доставка

Нотификатор разбирает до 50 ожидающих уведомлений каждые десять секунд в порядке
создания, в одной транзакции с `FOR UPDATE SKIP LOCKED`, поэтому два бота не могут
доставить одну и ту же строку. Каждое уведомление заканчивается ровно в одном
терминальном состоянии:

| Статус | Что означает |
| - | - |
| `pending` | В очереди, попытка ещё не предпринималась. Строки остаются здесь сколь угодно долго, пока группа не настроена. |
| `sent` | Доставлено, с записанным `sent_at`. |
| `suppressed` | Достигнут лимит объёма; `error_code = volume_cap`. |
| `failed` | Telegram отказал, с классифицированным `error_code`. |

Уведомление в статусе `failed` не повторяется. Строка и её полезная нагрузка
остаются в базе данных, поэтому запись о событии сохраняется даже тогда, когда
сообщение не дошло, но лекарство от устойчивого сбоя — исправить группу и
пропустить следующее событие, а не ждать повторного проигрывания.

## Контроль объёма

```bash theme={null}
APP_OPERATOR_NOTIFICATION_CAP=30
APP_OPERATOR_NOTIFICATION_WINDOW_MINUTES=5
```

Перед каждой отправкой нотификатор считает, сколько уведомлений этого вида было
**отправлено** внутри окна. На лимите или выше уведомление завершается как
`suppressed` с причиной `volume_cap`, и в лог пишется предупреждение с видом и
числом подавленных за этот проход.

Лимит действует на каждый вид отдельно, поэтому сбой Remnawave, порождающий тысячи
неудач выдачи доступа, не может похоронить поток покупок, а подавленные уведомления
сами в лимит не засчитываются. Ничего не теряется: строки остаются в базе данных
вместе со своими полезными нагрузками.

Установите лимит в `0`, чтобы отправлялось всё.

> [!WARNING]
>
> Группа, настроенная впервые после долгой работы, разбирает накопленную очередь.
> Очередь хранит каждое уведомление, записанное, пока группы не было, а разбор
> ограничен 50 штуками каждые десять секунд, и поверх этого действует лимит
> объёма, — поэтому бо́льшая часть крупного накопления будет доставлена как
> `suppressed`, а не как сообщения. Это ожидаемый исход; читайте историю в панели,
> а не рассчитывайте увидеть её в чате.

## Дедупликация

Каждое уведомление несёт ключ дедупликации: заказ — для покупки, возврат — для
возврата, операция выдачи доступа — для сбоя, событие и аккаунт — для уведомления
о безопасности. Пара `operator_notifications (kind, dedupe_key)` уникальна, а
вставка выполняется как `ON CONFLICT DO NOTHING`, поэтому повторённый вебхук,
второй проход сверки и раз за разом падающее задание дают одно сообщение.

Уведомление ставится в очередь в той же транзакции, что и событие, которое оно
описывает. Поэтому уведомление существует ровно тогда, когда то, о чём оно
сообщает, зафиксировано, — никогда для откаченного события и никогда не
отсутствует для зафиксированного. Сбой постановки в очередь логируется и никогда
не роняет вызвавшую его транзакцию, потому что состоявшийся платёж не должен
отменяться сообщением в чат.

## Что содержит уведомление

Рендерер печатает имя топика, затем только поля из фиксированного списка
разрешённых, затем отметку времени. Всё прочее, что случайно оказалось в полезной
нагрузке, отбрасывается, а не фильтруется, поэтому поле, добавленное в полезную
нагрузку в будущем, не может утечь по недосмотру:

```text theme={null}
orderId · subscriptionId · entitlementId · provider · operation · currency
amountMinor · creditedMinor · classification · reason · source · errorCode
fileName · sizeBytes · status · event · adminUserId · method
```

Поэтому уведомление о покупке выглядит так:

```text theme={null}
💳 Purchases
orderId: 018f0000-0000-7000-8000-000000000001
subscriptionId: 018f0000-0000-7000-8000-000000000002
operation: purchase
currency: RUB
amountMinor: 149900
at: 2026-08-11T10:15:00Z
```

Здесь нет ни имени клиента, ни аккаунта Telegram, ни ссылки на подписку, ни токена
провайдера, ни полезной нагрузки платежа. Идентификаторы включены именно потому,
что сами по себе они бесполезны: оператор смотрит их через панель или
административный API, где действуют авторизация и аудит.

Уведомление о безопасности называет событие, аккаунт оператора и метод, и больше
ничего. Адрес оператора, токен сессии и IP клиента остаются вне группового чата;
эти детали живут в журнале аудита, за правами доступа, которые им управляют. См.
[Панель администратора](https://omniflow-vpn.mintlify.site/ru/operations/admin-panel.md).

## Уведомления о сбоях выдачи доступа

Падающий прогон выдачи доступа не уведомляет сразу. Ожидается, что обычная
экспоненциальная пауза позволит восстановиться, поэтому оператору сообщают только
после трёх неудачных попыток или сразу при постоянной ошибке. Ключом дедупликации
служит операция, поэтому одно проблемное право на услугу порождает одно сообщение,
сколько бы раз оно ни повторялось.

Уведомление несёт право на услугу, операцию, текущий статус и классифицированный
код ошибки вроде `remnawave_http_502` или `remnawave_unavailable`. Тело ответа
панели оно не несёт никогда.

В тот же топик попадают два родственных случая:

* **Доставка цифрового товара**, которую провайдер не смог завершить.
* **Дополнение, оплаченное при отсутствии действующего права на услугу**, — заказ
  сохраняет статус оплаченного, поэтому деньги никогда не теряются молча, а
  уведомление вместе с событием аудита `addon.no_active_entitlement` говорит
  оператору ровно то, что нужно вернуть или применить заново.

## Связанные страницы

> **[Telegram](https://omniflow-vpn.mintlify.site/ru/integrations/telegram.md)**
>
> BotFather, вебхук против длинного опроса и группа операторов.

> **[Диагностика неисправностей](https://omniflow-vpn.mintlify.site/ru/operations/troubleshooting.md)**
>
> Что проверить, когда группа замолчала.


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