Что это
Webhooks — механизм уведомления вашей системы о событиях в кабинете в реальном времени. Вместо того чтобы периодически опрашивать API, вы регистрируете URL — и MCM шлёт POST-запрос на него при каждом событии.
Подписка
Через UI: /integrations → раздел «Webhooks» → форма «URL + события» → «+».
Через API:
POST /api/v1/webhooks
Authorization: Bearer mcm_...
Content-Type: application/json
{
"url": "https://crm.example.com/mcm-hook",
"events": ["call.end", "callback.requested"]
}
Ответ:
{
"ok": true,
"id": 7,
"secret": "1f2c3d...",
"note": "Сохраните secret — больше он не будет показан."
}
Этот secret используется для проверки подписи каждого пуша.
Формат пуша
POST https://crm.example.com/mcm-hook
Content-Type: application/json
X-MCM-Event: call.end
X-MCM-Signature: sha256=<hex>
User-Agent: mcm-webhook/1.0
{
"event": "call.end",
"tenant_id": "uuid",
"data": { ... },
"ts": "2026-05-01T22:30:00+00:00"
}
Проверка подписи
Python:
import hmac, hashlib
def verify(secret, body, header_signature):
expected = 'sha256=' + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header_signature)
# в обработчике:
sig = request.headers['X-MCM-Signature']
if not verify(SECRET, request.body, sig):
return Response(403)
PHP:
function verifyMcmWebhook(string $secret, string $body, string $sig): bool {
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
return hash_equals($expected, $sig);
}
Поддерживаемые события
call.start |
Начало звонка |
call.end |
Завершение звонка |
call.answered |
Ответ на звонок |
call.missed |
Пропущенный звонок |
chat.new |
Новый чат с сайта |
chat.message |
Новое сообщение в чате |
callback.requested |
Заказ обратного звонка из коллтрекинга/виджета |
transcript.ready |
Транскрипция готова |
* |
Все события |
Структура data по событиям
Конверт (event, tenant_id, ts) одинаков для всех событий, различается
только содержимое data. Ниже — состав полей и типы.
Звонки: call.start, call.answered, call.missed, call.end
Общие поля для всех четырёх событий:
| Поле | Тип | Описание |
|---|---|---|
call_id |
string | Идентификатор звонка целиком. Одинаков у всех событий одного звонка — используйте как ключ идемпотентности |
linkedid |
string | Синоним call_id |
uniqueid |
string | Идентификатор первого плеча звонка |
direction |
string | inbound — входящий, outbound — исходящий, internal — внутренний |
from |
string | Номер звонящего, только цифры |
to |
string | Номер вызываемого, только цифры |
did |
string | Ваш номер, на который поступил входящий. У исходящих пусто |
caller_name |
string | Имя звонящего, если передано оператором. Может быть пустым |
extension |
string | Внутренний номер сотрудника, участвовавшего в разговоре: у исходящих — кто звонил, у входящих — кто ответил. Пусто, если разговора с сотрудником не было (например, вызов завершился в IVR) |
started_at |
string | Начало звонка, ISO 8601 с часовым поясом |
tenant_domain |
string | Домен вашего кабинета |
Дополнительные поля отдельных событий:
| Событие | Поле | Тип | Описание |
|---|---|---|---|
| call.answered | answered_at |
string | Момент ответа, ISO 8601 |
| call.missed | status |
string | no_answer, busy или failed |
ring_time |
int | Сколько секунд шёл вызов до отбоя | |
| call.end | status |
string | answered, no_answer, busy, failed |
answered |
bool | Состоялся ли разговор | |
duration |
int | Секунды от начала вызова до отбоя | |
talk_time |
int | Секунды разговора. У неотвеченных 0 | |
recording_url |
string | null | Ссылка на запись. null, если записи нет. Открывается после входа в кабинет |
Пример call.end:
{
"event": "call.end",
"tenant_id": "3f7a1c92-0b64-4e18-9c25-7a1d5e8b04f6",
"ts": "2026-08-31T12:50:40+03:00",
"data": {
"call_id": "1750000000.12345",
"linkedid": "1750000000.12345",
"uniqueid": "1750000000.12345",
"direction": "outbound",
"from": "74951234567",
"to": "79001234567",
"did": "",
"caller_name": "",
"extension": "205",
"started_at": "2026-08-31T12:49:50+03:00",
"tenant_domain": "mcm000.smart.mcm.ru",
"status": "answered",
"answered": true,
"duration": 27,
"talk_time": 10,
"recording_url": "https://mcm000.smart.mcm.ru/statistic/recording/1750000000.12345"
}
}
Важно про время отправки. События call.start и call.answered отправляются
после завершения разговора, а не в момент самого события: данные о звонке фиксируются при его отбое.
Метки времени внутри (started_at, answered_at) верные, поэтому для журналирования
звонков в CRM этого достаточно. Для всплывающей карточки в момент входящего вызова webhooks не подходят —
используйте интеграции с CRM, где это реализовано отдельно.
chat.new — новый чат с сайта
| Поле | Тип | Описание |
|---|---|---|
session_id |
string | Идентификатор чата, ключ идемпотентности |
token |
string | Токен сессии посетителя |
visitor_name |
string | Имя, если посетитель представился |
visitor_phone |
string | Телефон, если оставлен |
visitor_email |
string | Почта, если оставлена |
page_url |
string | Страница, с которой начат чат, вместе с UTM-метками |
referrer |
string | Откуда посетитель пришёл на страницу |
visitor_ip |
string | IP-адрес посетителя |
started_at |
string | Начало чата, ISO 8601 |
chat.message — новое сообщение в чате
| Поле | Тип | Описание |
|---|---|---|
message_id |
string | Идентификатор сообщения, ключ идемпотентности |
session_id |
string | К какому чату относится |
from |
string | visitor или operator |
text |
string | Текст сообщения |
attachment_url |
string | null | Ссылка на вложение |
attachment_name |
string | null | Имя файла вложения |
created_at |
string | Время сообщения, ISO 8601 |
visitor_name |
string | Имя посетителя из чата |
visitor_phone |
string | Телефон посетителя из чата |
Служебные заметки операторов в webhooks не попадают.
callback.requested — заказ обратного звонка
| Поле | Тип | Описание |
|---|---|---|
callback_id |
string | Идентификатор заявки, ключ идемпотентности |
session_id |
string | null | Чат, из которого заказан звонок |
visitor_name |
string | Имя, оставленное в форме |
phone |
string | Номер для обратного звонка |
status |
string | Состояние обработки заявки |
notes |
string | Комментарий к заявке |
created_at |
string | Время заказа, ISO 8601 |
transcript.ready — транскрипция готова
| Поле | Тип | Описание |
|---|---|---|
transcript_id |
string | Идентификатор транскрипции, ключ идемпотентности |
call_id |
string | Звонок, к которому относится. Совпадает с call_id звонковых событий |
uniqueid |
string | Идентификатор плеча звонка |
call_date |
string | Дата и время звонка, ISO 8601 |
duration |
int | Длительность записи в секундах |
text |
string | Расшифровка с разделением на реплики оператора и клиента |
sentiment |
string | null | Тональность разговора, если определялась |
keywords |
string | null | Ключевые слова, если выделялись |
completed_at |
string | Когда расшифровка была готова, ISO 8601 |
recording_url |
string | null | Ссылка на запись разговора |
Поля sentiment и keywords заполняются не всегда — обрабатывайте null.
Retry policy
Если ваш сервер вернул статус не 2xx или не ответил — MCM повторяет доставку:
- Попытка 1 — сразу
- Попытка 2 — через 60 секунд
- Попытка 3 — через 5 минут
- Попытка 4 — через 30 минут
- Попытка 5 — через 2 часа
После 5 неудачных попыток событие помечается delivered_at = now() с last_error = "GIVE UP" и больше не отправляется. Статус последней попытки виден в /integrations.
Идемпотентность
Каждое событие имеет уникальный data.linkedid (для звонков) или data.session_id (для чатов). Используйте это поле как идемпотентность-ключ — при повторной доставке (retry) одного и того же события данные те же.
Типичные интеграции
- amoCRM/Bitrix24 — подпишитесь на
call.end,callback.requested. Поcall.endсоздавайте сделку/задачу; поcallback.requested— лид. - Slack/Telegram — на
call.missedшлите сообщение оператору с номером и временем. - Google Sheets / BI — на
call.endдобавляйте строку в таблицу для аналитики.