> For the complete documentation index, see [llms.txt](https://incy.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://incy.gitbook.io/docs/premium-api/push-uvedomleniya.md).

# Push-уведомления

Провайдеры с активным Premium могут рассылать push-уведомления своим подписчикам. Все сообщения проходят модерацию в INCY, кроме случая отправки на собственное устройство через [admin HWID](broken://pages/47e85f955b42ebe13c171895b9458c40537503a8).

***

## Жизненный цикл уведомления

{% stepper %}
{% step %}

### Создание

Провайдер жмёт «Отправить» в панели → запись кладётся в коллекцию `pendingNotifications` со статусом `pending`.
{% endstep %}

{% step %}

### Модерация

Бот INCY показывает заявку админу; тот нажимает «Одобрить» или «Отклонить с комментарием».
{% endstep %}

{% step %}

### Fan-out

При одобрении сервер выбирает устройства по `targetSegment`, раскладывает метаданные на их device-документы (для десктопного polling) и шлёт FCM-пуш на каждое устройство, у которого есть `fcmToken`.
{% endstep %}

{% step %}

### Статусы

Провайдер видит в панели статус и счётчики `deliveredCount` / `failedCount`.
{% endstep %}
{% endstepper %}

### Статусы записи

| Статус      | Значение                                                             |
| ----------- | -------------------------------------------------------------------- |
| `pending`   | Ждёт модерации                                                       |
| `approved`  | Одобрено модератором (или auto-approved); FCM-рассылка выполнена     |
| `rejected`  | Отклонено модератором. `moderatorComment` содержит причину           |
| `cancelled` | Провайдер отменил до одобрения. Рассылка не выполнялась              |
| `failed`    | Одобрено, но fan-out упал (нет верифицированных доменов / FCM error) |

***

## Отправка: поля уведомления

| Поле            | Тип       | Обязательное | Описание                                                     |
| --------------- | --------- | :----------: | ------------------------------------------------------------ |
| `title`         | `string`  |      да      | Заголовок, ≤ 100 символов                                    |
| `body`          | `string`  |      да      | Текст, ≤ 500 символов                                        |
| `image`         | `string?` |      нет     | URL большого изображения (отрисовывается в развёрнутом пуше) |
| `url`           | `string?` |      нет     | URL для кнопки / тапа по уведомлению                         |
| `urlButtonName` | `string?` |      нет     | Подпись кнопки. По умолчанию «Открыть»                       |
| `forceTimer`    | `number?` |      нет     | Enterprise-only: показать модальный диалог на N сек (1-10)   |
| `targetSegment` | `object`  |      да      | Параметры таргетинга (ниже)                                  |

### Таргетинг (`targetSegment`)

| Поле         | Тип         | Описание                                                                            |
| ------------ | ----------- | ----------------------------------------------------------------------------------- |
| `platform`   | `string`    | `all` \| `ios` \| `android` \| `linux` \| `windows` \| `macos`                      |
| `region`     | `string[]?` | Локаль устройства (например `["ru", "by"]`). Сравнение case-insensitive             |
| `domain`     | `string?`   | Конкретный домен подписки провайдера (если у провайдера их несколько)               |
| `activeDays` | `number?`   | Только устройствам, видимо активным за последние N дней                             |
| `appVersion` | `string?`   | Только конкретная версия приложения (например `"2.5.6"`)                            |
| `hwid`       | `string?`   | Конкретный HWID. Включает [admin-HWID auto-approve](#auto-approve-через-admin-hwid) |

Все фильтры объединяются логическим **И**. Пустые / неуказанные поля не ограничивают рассылку.

### Доставка

* **Android / iOS** получают пуш через FCM.
* **Desktop (Linux / Windows)** опрашивает собственный документ устройства в Firestore и забирает поля `pendingNotificationTitle`, `pendingNotificationBody`, `pendingNotificationAt`, `pendingNotificationUrl`, `pendingNotificationUrlButtonName`, `pendingNotificationForceTimer` — раздельно от FCM, поскольку десктоп без FCM.

### Ограничения

* Только для доменов с `isVerified: true` у провайдера.
* Только при активном Premium (`isPremium: true` в Firestore).
* `forceTimer` > 0 работает только на Enterprise-тарифе.

***

## Auto-approve через admin HWID

Если `targetSegment.hwid` совпадает с одним из `adminHwids` любого верифицированного домена провайдера — запись создаётся сразу со статусом `approved`, модерация пропускается, FCM уходит немедленно.

Подробности: [admin-hwids.md](broken://pages/47e85f955b42ebe13c171895b9458c40537503a8).

Это единственный путь, по которому провайдер может отправить уведомление в обход модерации.

***

## Отмена рассылки

Пока статус `pending`, провайдер может отменить уведомление в панели. В результате:

* Статус меняется на `cancelled`.
* Если модератор в этот момент решит «Одобрить» — бот получит ответ «Провайдер отменил рассылку» и FCM не выполнится.

После перевода в любой терминальный статус (`approved` / `rejected` / `cancelled` / `failed`) — операция необратима.

***

## Аудит

Каждая запись в `pendingNotifications` хранит:

| Поле                | Описание                                                     |
| ------------------- | ------------------------------------------------------------ |
| `providerId`        | UID провайдера                                               |
| `providerEmail`     | email провайдера на момент отправки                          |
| `createdAt`         | Когда провайдер нажал «Отправить»                            |
| `moderatedAt`       | Когда модератор решил / система auto-approved                |
| `moderatorComment`  | Текст отказа (для `rejected`)                                |
| `autoApproved`      | `true` если миновало модерацию                               |
| `autoApproveReason` | Причина, например `"adminHwid"`                              |
| `cancelledAt`       | Для `cancelled` — когда отменил провайдер                    |
| `cancelledBy`       | Источник отмены (сейчас только `"provider"`)                 |
| `deliveredCount`    | Число успешно отправленных пушей + desktop-polling устройств |
| `failedCount`       | FCM-ошибки                                                   |
