TL;DR: Assine calls.post, retorne HTTP 200 em até dois segundos e alimente a lógica do CRM com status em JSON flat (initiated, ringing, answered, missed, canceled). Deduplique por id mais timestamp. Você obtém o ciclo de vida observável completo sem WebRTC; a duração da chamada depende de um webhook terminate futuro.
O calls.post da Whapi entrega o ciclo de vida observável completo de chamadas via webhooks JSON flat, tanto em 1:1 quanto em grupo, sem WebRTC ou SDP. Seu backend Node.js reage a cada toque e atendimento em um handler HTTPS comum. O limite é explícito: esta surface da API não tem evento terminate, então a duração da chamada não está disponível hoje.
Times que conectam webhooks de mensagem primeiro e ignoram eventos de chamada perdem visibilidade no CRM no momento em que voz chega ao WhatsApp Web.
Chamadas em grupo no WhatsApp Web — por que webhooks de chamada importam agora
Chamadas de voz e vídeo em grupo no WhatsApp Web estão sendo liberadas em canais beta, com relatos de até 32 participantes, toque seletivo e links de chamada compartilháveis.
O WABetaInfo acompanhou o rollout de chamadas em grupo na Web como recurso de cliente. Sistemas backend ainda precisam de status server-side para chamadas em grupo no WhatsApp Web: quem tocou, quem atendeu, quem perdeu — entregue a um endpoint que você controla. É para isso que serve o calls.post: eventos de sinalização observáveis, não streams de mídia.
Um time de clínica com quem trabalhamos conectou eventos missed a um fluxo de retorno por texto em um dia, assim que o calls.post foi habilitado — sem stack WebRTC.
calls.post da Whapi em 60 segundos
Habilite calls.post na URL de webhook do canal, aceite payloads POST, responda rápido e persista cada linha de chamada antes de executar efeitos colaterais.
Webhooks JSON flat, não SDP WebRTC. Cada entrega envolve um ou mais objetos no array calls no topo. Os campos mapeiam diretamente do schema OpenAPI CallEvent: sem session description, sem ICE candidates, sem negociação de mídia no seu servidor.
Formato típico do envelope:
{
"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
}
]
}
Aponte a URL nas configurações do canal, assine eventos de chamada e teste a partir de um dispositivo vinculado. Veja a referência de formato de webhook de entrada para campos compartilhados do envelope. Respostas diferentes de 200 disparam retries.
Ciclo de vida da chamada: initiated → ringing → answered / missed / canceled
Initiated, ringing, answered, missed, canceled: observáveis sem mídia. O OpenAPI lista os cinco status em CallEvent.status. Seu handler deve tratar cada POST como transição de estado, não como notificação isolada.
Status: initiated
Dispara quando o objeto de chamada é criado, antes do celular do destinatário tocar. Use para reservar uma linha no CRM ou incrementar contadores de tentativa.
{
"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
}]
}
Status: ringing
Sinaliza toque ativo no lado do destinatário. Combine com initiated para medir latência de ring ou disparar screen-pop para agentes.
{
"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
}]
}
Status: answered
Caminho terminal de sucesso. Pare timers de retry, marque a conversa como ativa e encaminhe para o caminho de voz que seu produto usa fora deste 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
}]
}
Status: missed
Sem atendimento antes do timeout. Webhooks de chamada perdida disparam retorno por texto no CRM: mensagens de follow-up, criação de ticket ou inserção em fila de callback.
{
"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 também aparece no OpenAPI quando o caller desliga antes do atendimento ou o convite é retirado. Exemplos antigos do help desk às vezes listam só quatro status; confie no schema e trate canceled como ramo terminal próprio na sua máquina de estados.
Webhooks de chamada em grupo: diferenças de payload 1:1 vs grupo
group_call:true altera o formato do payload no nível booleano. O mesmo enum de status se aplica, mas chat_id aponta para um JID de grupo e a lógica de roteamento precisa distribuir para vários agentes.
Exemplo derivado do schema OpenAPI (valores ilustrativos até seu canal registrar uma chamada em grupo ao vivo). Para padrões de mensagens em grupo outbound, veja a visão geral da API de Grupos do WhatsApp.
{
"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
}]
}
| Campo | Chamada 1:1 (group_call: false) |
Chamada em grupo (group_call: true) |
|---|---|---|
chat_id |
JID individual (@s.whatsapp.net) |
JID de grupo (@g.us) |
from |
ID de contato do caller | ID de contato do caller (mesmo campo) |
enum status |
initiated → ringing → answered / missed / canceled | Mesmo enum; toque seletivo pode gerar vários posts ringing |
video_call |
Voz vs vídeo no convite | Igual; vídeo em grupo na Web usa a mesma flag |
| Roteamento CRM | Mapeie chat_id para um responsável |
Mapeie membership do grupo ou fila compartilhada; evite retornos por texto duplicados por participante |
Whapi vs Cloud API oficial: mapeamento de status
Na API oficial do WhatsApp Business, tutoriais de chamadas da Cloud API focam em fluxos WebRTC connect, troca de SDP e payloads terminate com duração. Na Whapi.Cloud, você recebe webhooks apenas de sinalização em sockets de sessão web, sem empurrar negociação de mídia para o seu servidor.
A Whapi mapeia ringing e answered; a Meta usa nomes de status diferentes no modelo de webhook VoIP. Terminate da Meta expõe duração; a Whapi não tem evento terminate. Planeje lógica de billing e encerramento de acordo. Para um enquadramento mais amplo de custo oficial vs não oficial, veja por que a API oficial muitas vezes não atende times pequenos.
| Status de chamada Cloud API Meta (referência) | status do calls.post Whapi |
Notas |
|---|---|---|
| Connect / RINGING | ringing |
Ambos sinalizam toque ativo; só muda a nomenclatura |
| ACCEPTED | answered |
Destinatário atendeu; mídia permanece no cliente na Whapi |
| REJECTED | missed ou canceled |
Whapi separa rejeição explícita vs timeout no enum |
| Terminate (inclui duração) | não disponível | Sem webhook de fim de chamada nesta surface hoje |
| -- | initiated |
Sinal antecipado antes do ring; útil para preparar o CRM |
Esperar duração estilo Terminate só do calls.post é lacuna de cobertura da API, não erro de configuração de webhook.
Construindo um handler de eventos de chamada: padrão de máquina de estados
Modele cada call.id como uma linha em um ledger de estado de chamada. Avance só quando o status recebido superar o rank armazenado e o timestamp for mais recente.
Responda webhooks com 200 rápido; deduplique por id e timestamp da chamada. Enfileire efeitos colaterais do CRM de forma assíncrona. O mesmo padrão evento-reação se aplica aqui: receber, persistir, reagir. Handlers lentos causam retries e linhas duplicadas.
// 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);
}
}
});
Pseudocódigo para a guarda de transição:
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
Processar antes do ACK 200 frequentemente duplica efeitos colaterais de chamada perdida no retry. A Whapi usa sockets de sessão web, então canais permanecem estáveis o suficiente para automatizar eventos de chamada. Veja o tutorial de bot WhatsApp em Node.js para um starter completo de webhook.
O que calls.post não consegue informar
A duração da chamada não está disponível nesta surface de webhook. O suporte Whapi confirma que não há evento terminate ou de fim de chamada para calcular segundos em linha.
Terminate da Meta expõe duração; a Whapi não tem evento terminate. Não infira tempo de conversa a partir de timestamps de answered. Provisionamento no Meta App Dashboard e debug de SDP pertencem a tutoriais VoIP da Cloud API, não às configurações de canal Whapi.
Campos do payload: video_call, offline_call, latency
video_call, offline_call, latency explicam contexto de entrega, não mídia. Eles não substituem uma API de gravações ou metadados de stream.
video_call marca convites de voz vs vídeo; offline_call sinaliza entrega offline; latency reporta milissegundos de sinalização (não qualidade RTP).
Combine as três flags com status ao priorizar callbacks de agentes: um video_call missed com latency alta e offline_call: true frequentemente significa que o destinatário nunca viu o toque na Web.
Próximos passos: iniciação de chamadas na Whapi
Hoje você observa chamadas a partir de clientes WhatsApp vinculados; chamadas outbound programáticas e webhooks de fim de chamada estão no roadmap de curto prazo.
POST /calls e webhooks de fim de chamada no roadmap. O endpoint create-call já existe para criação de eventos; espere surfaces mais ricas de iniciação e encerramento conforme chamadas em grupo na Web amadurecem.
# 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"}'
A Whapi está estendendo ativamente as APIs de chamada conforme clientes Web ganham voz e vídeo em grupo. Assine calls.post, construa o ledger de estado e plugue duração quando eventos terminate forem lançados. Acompanhe releases no changelog do produto. O ciclo de vida observável completo já chega como JSON flat para chamadas 1:1 e em grupo; duração aguarda o próximo tipo de webhook.









