WebSocket событий
GET /api/v1/events переходит в WebSocket, который передаёт активные события вашей компании в виде текстовых кадров JSON. Так интеграция отслеживает звонки и изменения настроек. MonoPbx не обращается к вашему серверу: исходящих вебхуков пока нет.
Полезная нагрузка событий — это JSON-конверты; их схема JSON отдаётся по адресу GET /api/v1/events/schema.
Подключение
Сначала пройдите аутентификацию, затем оформляйте подписку. До успешной аутентификации ничего не подписывается, не повторяется и не отправляется.
| Клиент | Как пройти аутентификацию |
|---|---|
| Сервер, CLI, интеграция | Отправьте Authorization: Bearer <access token or mpbx_k1_ API key> в запросе на переход. Неверные учётные данные дают обычный 401 и без сокета. |
| Браузер (WebSocket в браузере не может задавать заголовки) | Подключитесь без учётных данных и отправьте {"op":"auth","token":"..."} как первый кадр в течение 5 секунд. Любой другой первый кадр, неверный токен или молчание закрывают сокет с кодом 4401. |
Никогда не передавайте токен в строке запроса. URL попадают в журналы. Cookie на этом маршруте тоже никогда не принимаются, поэтому страница на другом сайте не может открыть сокет с вашей сессией.
Системный субъект выбирает компанию через X-Mpbx-Tenant. Учётные данные компании не могут выбрать другую компанию (403).
// Browser
const ws = new WebSocket("wss://pbx.example.com/api/v1/events");
ws.onopen = () => ws.send(JSON.stringify({ op: "auth", token: accessToken }));
ws.onmessage = (m) => {
const f = JSON.parse(m.data);
if (f.op === "ready") ws.send(JSON.stringify({ op: "subscribe", topics: ["call", "presence"] }));
if (f.op === "event") handle(f.event, f.cursor);
};
По желанию передайте topics и cursor в строке запроса (?topics=call,presence&cursor=<event id>), чтобы подписаться сразу после ready. Без них сокет ждёт кадра subscribe.
Кадры
От клиента к серверу:
| Кадр | Значение |
|---|---|
{"op":"auth","token":"..."} | Первый кадр сокета, который не передал учётные данные в заголовке. |
{"op":"subscribe","topics":["call","presence"],"cursor":"<event id>"} | Добавляет темы. Если не указать topics (или передать пустой список), подписка будет на все темы, которые вы можете видеть. cursor возобновляет доставку после этого события и принимается только в первом subscribe. |
{"op":"unsubscribe","topics":["presence"]} | Удаляет темы. |
{"op":"ping"} | Получает ответ {"op":"pong"}. |
От сервера к клиенту:
| Кадр | Значение |
|---|---|
{"op":"ready","protocol":1,"tenant_id":"...","topics":[...],"heartbeat_secs":20} | Аутентификация пройдена. topics — темы, на которые вы можете подписаться. |
{"op":"subscribed","topics":[...],"denied":[...],"resync_required":false} | Активные темы. denied перечисляет запрошенные темы, которые вы не можете видеть. |
{"op":"event","cursor":"<event id>","event":{...}} | Одно событие. cursor равен event.id. |
{"op":"replay_done","count":3} | События после вашего cursor отправлены; далее идут текущие события. |
{"op":"heartbeat","ts":1790000000000} | Признак работоспособности, каждые heartbeat_secs. |
{"op":"resync_required","reason":"events_lost"} | Сервер потерял события. Загрузите состояние заново через REST. |
{"op":"error","code":"MPBX-EVENTS-WS-...","detail":"..."} | Отклонённый кадр. Сокет остаётся открытым, кроме случая повторных некорректных кадров (закрытие 1008). |
Доставка выполняется не менее одного раза: убирайте дубликаты по id события. События от одного источника приходят по порядку.
Темы и права доступа
Вы получаете только события своей компании и только те, которые разрешает ваша роль:
| Тема | Нужное право | Область |
|---|---|---|
call, leg | calls:control | отдел |
queue | queues:read | отдел |
presence | directory:read | вся компания |
voicemail | voicemail:read | отдел |
config | право чтения изменяемого объекта (например, users:read для пользователя) | |
audit | audit:read | вся компания |
device | devices:read | отдел |
Вошедший пользователь всегда видит события, в которых участвует (свои звонки, присутствие и почтовый ящик). Ключ API этого не получает: он видит только то, что разрешают его области доступа. Изменение роли вступает в силу в течение 60 секунд, а просроченные или отозванные учётные данные закрывают сокет с кодом 4401 за то же время.
События звонков и участков звонка (leg), очередей, присутствия и голосовой почты публикуются по мере появления этих функций, поэтому некоторые темы пока могут не содержать событий.
Возобновление после разрыва
Сохраняйте cursor последнего обработанного события. После переподключения откройте сокет с ?cursor=<it> (или передайте его в первом subscribe). Сервер отправит события вашей компании после него, затем replay_done, затем текущие события.
Сервер хранит последние 8192 события в памяти. Если ваш cursor вне этого окна, сервер был перезапущен или идентификатор не относится к событиям вашей компании, subscribed.resync_required равен true, и ничего не повторяется: загрузите состояние заново через REST, затем продолжайте по текущему потоку. Надёжное повторение после перезапусков пока не реализовано.
Ограничения
| Параметр | По умолчанию |
|---|---|
| Признак работы (heartbeat) | каждые 20 с |
| Тайм-аут простоя (нет кадров от вас; pong тоже учитывается) | 60 с, затем закрытие 4408 |
Время на отправку кадра auth | 5 с |
| Кадров в очереди на соединение | 256; очередь, которая остаётся полной 5 с, закрывает сокет (1013) |
| Наибольший клиентский кадр | 16 КиБ |
| Открытых соединений | 10 000 на установку, 5 000 на компанию, 20 на пользователя или ключ |
Клиент, который читает слишком медленно, отключается и никогда не замедляет других; переподключитесь с вашим cursor.
Коды закрытия: 4401 — не аутентифицирован или учётные данные истекли; 4403 — компания не выбрана; 4408 — простой; 4429 — слишком много соединений (или закрыто, чтобы освободить место для другой компании); 1008 — злоупотребление протоколом; 1011 — ошибка сервера; 1012 — сервер перезапускается; 1013 — медленный получатель. При 1011, 1012 и 1013 переподключитесь с вашим cursor.
Звонок по клику
POST /api/v1/calls/click-to-call с {"destination":"+15550123","device_id":"<uuid>"} — опубликованный контракт, который сейчас отвечает 501 (MPBX-NOT-IMPLEMENTED). Он проверяет вызывающего и тело запроса (400 для неверного адреса назначения), затем останавливается. Инициирование звонка из API пока не реализовано; запрос и ответ не изменятся, когда это появится.