Оглавление
Нотификации
Что такое нотификации
Нотификация — это HTTP POST-запрос, который MassAccess отправляет на ваш сервер при наступлении события в системе. Когда изменяется статус сообщения, обновляется номер телефона клиента или клиент взаимодействует с ботом — ваш сервер получает JSON с данными события. Никаких дополнительных API-вызовов не требуется — нотификации отправляются автоматически.
Запросы отправляются на Webhook URL, настроенный в панели управления вашей компании (Настройки → Webhook URL). Каждый запрос содержит HMAC-SHA256 подпись (заголовок X-Signature), чтобы вы могли проверить его подлинность.
Как настроить нотификации
Чтобы начать получать нотификации, войдите в панель управления компании, перейдите в раздел «Настройки» и укажите URL вашего сервера в поле «Webhook URL». После сохранения MassAccess начнёт отправлять HTTP POST-запросы на этот URL для каждого события в вашей компании.
Кнопка «Проверить» отправляет один подписанный тестовый запрос с event = webhook.test и data.test = true. Запрос содержит те же прикладные заголовки доставки, что и бизнес-события: X-Signature, X-Webhook-Id, Idempotency-Key и X-Webhook-Attempt. Адрес считается доступным, если сервер отвечает кодом HTTP 2xx; 4xx означает, что сервер доступен, но отверг запрос, а 5xx, тайм-аут или сетевая ошибка — что endpoint недоступен. Проверка подтверждает только приём безопасного webhook.test, но не гарантирует приём каждого типа событий. Полная проверка выполняется реальной доставкой или через /api/v1/webhooks/replay.
Типы событий
MassAccess отправляет нотификации для следующих событий:
| Событие | Описание |
|---|---|
| message.status | Отправляется при переходе сообщения в терминальный статус: sent или failed. Поле data.state содержит текущий статус. Если data.state равен "failed", дополнительно включаются поля data.error_code и data.error_description. |
| client.phone.changed | Отправляется при изменении номера телефона клиента через эндпоинт /api/v1/change_phone. Поле data.state равно "completed" при успехе. Если изменение не удалось, data.state равен "failed" с полями error_code и error_description. |
| client.check | Отправляется при проверке существования клиента по номеру телефона через эндпоинт /api/v1/client/check. data.exists указывает, найден ли клиент, а data.bots содержит список ботов, к которым он подключён. |
| client.registered | Отправляется при регистрации нового клиента через вашего бота в Telegram или MAX. Полезная нагрузка включает phone, bot_name и service. Это событие отправляется один раз при регистрации клиента. |
Формат нотификации
Формат входящих webhook-уведомлений о статусе сообщений. Уведомления отправляются на URL из настроек компании.
Заголовки запроса
| Заголовок | Описание |
|---|---|
| X-Signature | HMAC-SHA256 подпись тела запроса |
| X-Webhook-Id | Стабильный UUID события; совпадает с event_id в теле. |
| Idempotency-Key | Ключ дедупликации; совпадает с X-Webhook-Id. |
| X-Webhook-Attempt | Номер попытки доставки, начиная с 1. |
| Content-Type | application/json |
| User-Agent | MassAccess-Webhook/2.0 |
Структура полезной нагрузки
Каждое тело нотификации — это объект JSON с общей структурой. Поля верхнего уровня присутствуют всегда; содержимое поля data зависит от типа события. Ниже приведены примеры для каждого типа события.
Изменение статуса сообщения
Отправляется, когда сообщение успешно доставлено получателю. data.state = sent.
{
"event": "message.status",
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2025-01-15T10:30:05Z",
"data": {
"state": "sent"
}
}Изменение статуса сообщения (error)
Отправляется при ошибке доставки. data.state = failed + error_code и error_description.
{
"event": "message.status",
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2025-01-15T10:30:05Z",
"data": {
"state": "failed",
"error_code": "NETWORK_ERROR",
"error_description": "Network error"
}
}Смена номера телефона
Отправляется при успешной смене номера через API /api/v1/change_phone. data.state = completed.
{
"event": "client.phone.changed",
"event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2025-01-15T10:30:05Z",
"data": {
"state": "completed"
}
}Смена номера телефона (ошибка)
Отправляется при ошибке смены номера. data.state = failed + error_code и error_description.
{
"event": "client.phone.changed",
"event_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"timestamp": "2025-01-15T10:30:05Z",
"data": {
"state": "failed",
"error_code": "INVALID_PHONE",
"error_description": "Invalid phone number format"
}
}Регистрация клиента в боте
Отправляется при регистрации нового клиента через бота (Telegram/MAX). Содержит phone, bot_name, service. Однократно.
{
"event": "client.registered",
"event_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"timestamp": "2025-01-15T10:30:05Z",
"data": {
"phone": "12345678901",
"bot_name": "my_telegram_bot",
"service": "telegram"
}
}Проверка клиента (exists = true)
Отправляется, когда клиент с указанным номером найден в компании. data.exists = true, data.bots содержит список ботов с полями name и service.
{
"event": "client.check",
"event_id": "3bd290b8-09b4-48c4-920b-85b93fc3859b",
"timestamp": "2026-07-28T21:45:31Z",
"data": {
"exists": true,
"bots": [
{
"name": "main_max",
"service": "max"
}
]
}
}Проверка клиента (exists = false)
Отправляется, когда клиент с указанным номером не найден. data.exists = false, data.bots пуст.
{
"event": "client.check",
"event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2026-07-28T21:45:31Z",
"data": {
"exists": false,
"bots": []
}
}Поля нотификации
| Имя | Тип | Обязателен | Описание |
|---|---|---|---|
| event | string | Да | Тип события: message.status | client.phone.changed | client.registered |
| event_id | string (UUID) | Да | Уникальный идентификатор сообщения (UUID) |
| timestamp | string (ISO 8601) | Да | Время события в ISO 8601 UTC |
| data.state | string | Нет | Статус сообщения: new, processing, sent, failed |
| data.error_code | string | Нет | Код ошибки (только при ошибке) |
| data.error_description | string | Нет | Человекочитаемое описание ошибки (только при ошибке) |
| data.client_id | integer | Нет | Client identifier (for client.* events) |
| data.phone | string | Нет | Client phone number (for client.* events) |
| data.bot_name | string | Нет | Bot name that triggered the event (for client.* events) |
| data.exists | boolean | Нет | Whether the client exists (client.check event) |
| data.bots | array | Нет | List of bots the client is connected to (client.check event). Each bot has name (string) and service (string) fields. |
Архитектура отправки
Ответ вашего сервера
Для подтверждения получения уведомления ваш сервер должен вернуть HTTP 200 OK. Если ответ не 200 — MassAccess будет повторять отправку.
Ваш сервер должен ответить в течение 10 секунд. Если сервер возвращает статус, отличный от 2xx, или время истекло, MassAccess повторяет доставку с экспоненциальной задержкой. После 5 неудачных попыток нотификация отбрасывается.
| HTTP Status | Описание |
|---|---|
| 200 OK | Нотификация доставлена успешно — MassAccess прекращает повторные попытки. |
| 4xx / 5xx | Доставка нотификации не удалась — MassAccess повторит попытку с экспоненциальной задержкой (задержки: ~1с, ~5с, ~25с, ~125с, ~625с). |
Проверка подписи
Заголовок X-Signature содержит HMAC-SHA256 подпись сырого тела запроса (до парсинга JSON). Чтобы проверить, что нотификация пришла от MassAccess:
- Получите сырое тело HTTP-запроса как байты (не парсите JSON сразу)
- Вычислите HMAC-SHA256 от сырых байтов тела, используя ваш API-ключ в качестве секрета
- Сравните полученную HEX-строку с заголовком X-Signature. Используйте специальную функцию безопасного сравнения из стандартной библиотеки вашего языка (например: compare_digest в Python, hash_equals в PHP, timingSafeEqual в Node.js), а не обычный оператор ==. Безопасное сравнение всегда занимает одинаковое время — это не позволяет злоумышленнику подбирать подпись по времени ответа сервера. Если строки не совпали — отклоните запрос, он поддельный.
Проверяйте заголовок X-Signature (HMAC-SHA256) чтобы убедиться в подлинности уведомления:
import hmac
import hashlib
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
"""Verify webhook HMAC-SHA256 signature."""
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)Коды ошибок нотификаций
Справочник кодов ошибок, которые могут передаваться в поле error_code webhook-нотификаций (message.status, client.phone.changed и др.). Для каждого кода указан мессенджер, в котором он может возникнуть: Telegram, MAX, оба, либо «—» для ошибок, не связанных с конкретным мессенджером.
Доставка
| Код | Описание | Мессенджер |
|---|---|---|
| TOKEN_INVALID | Токен бота недействителен или отозван мессенджером. Проверьте токен в настройках бота. | Telegram · MAX |
| BAD_REQUEST | Мессенджер отклонил запрос из-за некорректных параметров сообщения. | Telegram · MAX |
| RATE_LIMIT | Превышен лимит частоты отправки мессенджера. Сообщение будет повторено с задержкой. | Telegram · MAX |
| WEBHOOK_CONFLICT | Конфликт webhook Telegram: другой сервер уже установил webhook для этого бота. | Telegram |
| NETWORK_ERROR | Сетевая ошибка при обращении к API мессенджера (обрыв соединения, недоступность). | Telegram · MAX |
| DNS_ERROR | Не удалось разрешить доменное имя API мессенджера. | Telegram · MAX |
| SSL_ERROR | Ошибка TLS/SSL при установке защищённого соединения с API мессенджера. | Telegram · MAX |
| TIMEOUT | Превышено время ожидания ответа от API мессенджера. | Telegram · MAX |
| CONNECTION_CLOSED | Соединение с API мессенджера было закрыто до получения ответа. | Telegram · MAX |
| PROXY_ERROR | Ошибка прокси-релея (промежуточного сервера доставки). | Telegram · MAX |
| CONFIG_MISSING | Не задан URL webhook мессенджера в настройках платформы. | Telegram · MAX |
| DELIVERY_FAILED | Общая ошибка доставки сообщения. Причина не классифицирована или неизвестна. | Telegram · MAX |
Получатель
| Код | Описание | Мессенджер |
|---|---|---|
| USER_BLOCKED_BOT | Получатель заблокировал бота. Telegram: HTTP 403 «bot was blocked by the user»; MAX: chat.denied. Доставка завершается без повторных попыток. | Telegram · MAX |
| USER_NOT_FOUND | Получатель не найден (не зарегистрирован в мессенджере). | Telegram · MAX |
| USER_INACTIVE | Получатель неактивен или недоступен в мессенджере. | Telegram · MAX |
| DIALOG_SUSPENDED | Диалог приостановлен: MAX вернул error.dialog.suspended (получатель заблокировал бота или действует ограничение модератора). | MAX |
Смена номера
| Код | Описание | Мессенджер |
|---|---|---|
| PHONE_NEVER_EXISTED | Номер никогда не был зарегистрирован ни в одном мессенджере. | — |
| PHONE_ALREADY_INACTIVE | Номер уже неактивен. | — |
| PHONE_MULTIPLE_CLIENTS | Номер привязан к нескольким разным учётным записям. | — |
| PHONE_NOT_LINKED | Номер не привязан ни к одной учётной записи. | — |
| PHONE_NOT_IN_BOT | Новый номер никогда не был зарегистрирован ни в одном мессенджере. | — |
| PHONE_NOT_ACTIVE | Новый номер неактивен. | — |
| MIGRATION_SAME_CLIENT | Старый и новый номера уже привязаны к одной учётной записи. | — |