Кратко: Подключите calls.post, верните HTTP 200 в течение двух секунд и управляйте CRM через плоские JSON-статусы (initiated, ringing, answered, missed, canceled). Дедупликация по id и timestamp. Полный наблюдаемый жизненный цикл без WebRTC; длительность звонка появится после webhook terminate.
Whapi calls.post передаёт полный наблюдаемый жизненный цикл звонка через плоские JSON webhook для звонков 1:1 и групповых — без WebRTC и SDP. Ваш Node.js backend реагирует на каждый звонок и ответ из обычного HTTPS-обработчика. Честное ограничение: на этом уровне API нет события terminate, поэтому длительность звонка сегодня недоступна.
Команды, которые сначала подключают webhook сообщений и пропускают события звонков, теряют видимость в CRM, как только голос появляется в WhatsApp Web.
Групповые звонки WhatsApp в Web — почему вебхуки звонков важны уже сейчас
Групповые голосовые и видеозвонки в WhatsApp Web выходят через beta-каналы: до 32 участников, выборочный обзвон и ссылки на звонок.
WABetaInfo отслеживает rollout групповых звонков в Web как клиентскую функцию. Backend-системам по-прежнему нужен серверный статус групповых звонков WhatsApp в Web: кто звонил, кто ответил, кто пропустил — на endpoint, который вы контролируете. Для этого и нужен calls.post: наблюдаемые события сигнализации, а не медиапотоки.
Одна клиника, с которой мы работали, за день подключила пропущенные звонки к workflow с текстовым ответом после включения calls.post — без стека WebRTC.
Whapi calls.post за 60 секунд
Включите calls.post на URL webhook канала, принимайте POST payload, отвечайте быстро и сохраняйте каждую строку звонка до побочных эффектов.
Плоские JSON webhook, а не WebRTC SDP. Каждая доставка оборачивает один или несколько объектов в массив calls верхнего уровня. Поля напрямую соответствуют схеме OpenAPI CallEvent: без session description, без ICE candidates, без медиасогласования на вашем сервере.
Типичная форма конверта:
{
"event": { "type": "calls", "event": "post" },
"channel_id": "YOUR-CHANNEL-ID",
"calls": [
{
"id": "3EB0C767F26DEECBBE",
"chat_id": "[email protected]",
"status": "ringing",
"from": "[email protected]",
"timestamp": 1721641200,
"group_call": false,
"video_call": true,
"offline_call": false,
"latency": 842
}
]
}
Укажите URL в настройках канала, подпишитесь на события звонков и протестируйте с привязанного устройства. Общие поля конверта — в справочнике формата входящих webhook. Ответы не-200 вызывают повторные попытки.
Жизненный цикл звонка: initiated → ringing → answered / missed / canceled
Initiated, ringing, answered, missed, canceled — наблюдаемы без медиа. OpenAPI перечисляет все пять статусов в CallEvent.status. Обработчик должен трактовать каждый POST как переход состояния, а не как отдельное уведомление.
Статус: initiated
Срабатывает при создании объекта звонка, до звонка на устройстве абонента. Используйте для резервирования строки CRM или счётчиков попыток.
{
"calls": [{
"id": "3EB0A1B2C3D4E5F6",
"chat_id": "[email protected]",
"status": "initiated",
"from": "[email protected]",
"timestamp": 1721641180,
"group_call": false,
"video_call": false,
"offline_call": false,
"latency": 120
}]
}
Статус: ringing
Сигнал активного звонка на стороне абонента. В паре с initiated — для замера задержки до звонка или screen-pop для агентов.
{
"calls": [{
"id": "3EB0A1B2C3D4E5F6",
"chat_id": "[email protected]",
"status": "ringing",
"from": "[email protected]",
"timestamp": 1721641184,
"group_call": false,
"video_call": false,
"offline_call": false,
"latency": 310
}]
}
Статус: answered
Успешный терминальный путь. Остановите таймеры повторов, отметьте разговор активным и передайте управление тому голосовому пути, который использует ваш продукт вне этого webhook.
{
"calls": [{
"id": "3EB0A1B2C3D4E5F6",
"chat_id": "[email protected]",
"status": "answered",
"from": "[email protected]",
"timestamp": 1721641192,
"group_call": false,
"video_call": true,
"offline_call": false,
"latency": 905
}]
}
Статус: missed
Никто не ответил до таймаута. Webhook пропущенных звонков запускают текстовый ответ в CRM: follow-up сообщения, создание тикета или постановку в очередь обратного звонка.
{
"calls": [{
"id": "3EB0A1B2C3D4E5F6",
"chat_id": "[email protected]",
"status": "missed",
"from": "[email protected]",
"timestamp": 1721641240,
"group_call": false,
"video_call": false,
"offline_call": true,
"latency": 2100
}]
}
canceled тоже есть в OpenAPI, когда звонящий сбросил до ответа или приглашение отозвано. В старых примерах help-desk иногда только четыре статуса; ориентируйтесь на схему и обрабатывайте canceled как отдельную терминальную ветку в state machine.
Webhook групповых звонков: отличия payload 1:1 и группы
group_call:true меняет форму payload на уровне boolean. Тот же enum статусов, но chat_id указывает на group JID, а маршрутизация должна раздавать события нескольким агентам.
Пример по схеме OpenAPI (значения полей иллюстративны, пока канал не залогирует живой групповой звонок). Паттерны исходящих групповых сообщений — в обзоре WhatsApp Groups API.
{
"calls": [{
"id": "3EB0GROUPCALL001",
"chat_id": "[email protected]",
"status": "ringing",
"from": "[email protected]",
"timestamp": 1721641300,
"group_call": true,
"video_call": true,
"offline_call": false,
"latency": 640
}]
}
| Поле | Звонок 1:1 (group_call: false) |
Групповой звонок (group_call: true) |
|---|---|---|
chat_id |
Individual JID (@s.whatsapp.net) |
Group JID (@g.us) |
from |
ID контакта звонящего | ID контакта звонящего (то же поле) |
enum status |
initiated → ringing → answered / missed / canceled | Тот же enum; выборочный обзвон может дать несколько POST ringing |
video_call |
Голос или видео для приглашения | То же; групповое видео в Web использует тот же флаг |
| Маршрутизация CRM | Сопоставьте chat_id с одним владельцем |
Членство группы или общая очередь; избегайте дублирующих text-back на участника |
Whapi и официальный Cloud API: сопоставление статусов
В официальном WhatsApp Business API туториалы Cloud API по звонкам строятся вокруг WebRTC connect, обмена SDP и payload terminate с длительностью. В Whapi.Cloud вы получаете webhook только сигнализации через web-session sockets — без переноса медиасогласования на сервер.
Whapi сопоставляет ringing и answered; Meta использует другие имена статусов в модели VoIP webhook. Meta Terminate отдаёт длительность; у Whapi нет события terminate. Планируйте биллинг и wrap-up соответственно. Шире про official vs unofficial — почему официальный API часто не подходит малым командам.
| Статус звонка Meta Cloud API (справочно) | Whapi calls.post status |
Примечания |
|---|---|---|
| Connect / RINGING | ringing |
Оба сигнализируют активный звонок; отличаются только имена |
| ACCEPTED | answered |
Абонент ответил; медиа остаётся на клиенте в Whapi |
| REJECTED | missed или canceled |
Явный отказ и таймаут разделены в enum Whapi |
| Terminate (с длительностью) | недоступно | Webhook окончания звонка на этом уровне API пока нет |
| -- | initiated |
Дополнительный ранний сигнал до звонка; удобен для подготовки CRM |
Ожидать длительность в стиле Terminate только от calls.post — пробел в покрытии API, а не ошибка конфигурации webhook.
Обработчик событий звонка: паттерн state machine
Каждый call.id — строка в журнале состояний звонков. Переход только если входящий status выше сохранённого rank и timestamp новее.
Отвечайте на webhook быстро с 200; дедупликация по call id и timestamp. Побочные эффекты CRM — в очередь асинхронно. Тот же паттерн event-reaction: принять, сохранить, отреагировать. Медленные обработчики вызывают повторы и дубликаты строк.
// Returning 500 here retries the webhook and can double-send CRM text-backs
app.post('/webhooks/whapi', express.json(), async (req, res) => {
res.sendStatus(200); // ACK first; process after response
for (const call of req.body.calls ?? []) {
const key = `${call.id}:${call.status}:${call.timestamp}`;
if (await seen(key)) continue;
const prev = await db.getCall(call.id);
const next = rank(call.status); // initiated<ringing<answered|missed|canceled
if (prev && (next < prev.rank || call.timestamp < prev.timestamp)) continue;
await db.upsertCall({ ...call, rank: next });
if (call.status === 'missed') {
await queueTextBack(call.chat_id, call.from);
}
}
});
Псевдокод для guard перехода:
STATE ranks: initiated=1, ringing=2, answered=3, missed=3, canceled=3
ON calls.post:
ACK 200 immediately
FOR each call in payload:
IF dedupe_key(id, status, timestamp) exists: SKIP
IF stored.rank > new.rank: SKIP
IF stored.timestamp > call.timestamp: SKIP
UPSERT call row
IF status == missed AND policy allows: ENQUEUE text-back job
IF status == answered: CANCEL pending retry timers
Обработка до ACK 200 часто дублирует побочные эффекты пропущенных звонков при повторе. Whapi использует web-session sockets, каналы достаточно стабильны для автоматизации событий звонков. Полный старт webhook — в туториале WhatsApp-бота на Node.js.
Чего calls.post не сообщает
Длительность звонка недоступна на этом webhook. Поддержка Whapi подтверждает: нет terminate или call-end для подсчёта секунд на линии.
Meta Terminate отдаёт длительность; у Whapi нет terminate. Не выводите время разговора из timestamp answered. Provisioning в Meta App Dashboard и отладка SDP — из туториалов Cloud API VoIP, а не из настроек канала Whapi.
Поля payload: video_call, offline_call, latency
video_call, offline_call, latency описывают контекст доставки, не медиа. Они не заменяют API записей или метаданные потока.
video_call — голос или видео приглашения; offline_call — offline-доставка; latency — миллисекунды сигнализации (не качество RTP).
Сочетайте три флага со status при приоритизации callback агентов: пропущенный video_call с высокой latency и offline_call: true часто значит, что абонент не видел звонок в Web.
Что дальше: инициация звонков в Whapi
Сегодня вы наблюдаете звонки с привязанных клиентов WhatsApp; программный исходящий обзвон и webhook окончания звонка — в ближайшем roadmap.
POST /calls и webhook окончания звонка в roadmap. Endpoint create-call уже есть для создания события; более богатые поверхности инициации и завершения — по мере созревания групповых звонков в Web.
# POST https://gate.whapi.cloud/calls - create call event (start_time required today)
curl -X POST "https://gate.whapi.cloud/calls" \
-H "Authorization: Bearer $WHAPI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"start_time":"1721641200"}'
Whapi активно расширяет call API по мере появления группового голоса и видео в Web-клиентах. Подключите calls.post, постройте журнал состояний и добавьте длительность, когда появятся события terminate. Релизы — в changelog продукта. Полный наблюдаемый жизненный цикл уже приходит плоским JSON для 1:1 и группы; длительность ждёт следующий тип webhook.









