Você precisa de uma conexão WhatsApp já conectada (provider
whatsmeow) e
de uma API Key. Gere a sua chave no painel.Como funciona
1
Ative o recebimento na conexão
POST /sessions/{sessionId}/receive-calls-settings liga a capacidade. A
conexão reinicia.2
Assine os eventos
Abra o WebSocket
/voip/events e fique escutando. A oferta (call.offer)
chega por ali.3
Atenda ou recuse
POST /calls/accept devolve a wsUrl da mídia. POST /calls/reject
encerra a oferta.4
Troque áudio
Abra a
wsUrl e siga exatamente o fluxo da página de
Ligações por Stream.accept.
Passo 1 — Ative o recebimento na conexão
O recebimento vem desligado por padrão. Enquanto estiver desligado, a conexão não engaja chamadas entrantes: você continua recebendo o webhookcall.offer, mas a mídia não sobe e a chamada não
é atendível pelo gateway.
Se a atualização da variável de ambiente falhar, o valor continua persistido e
passa a valer na próxima subida do pod. A resposta ainda é de sucesso
nesse caso.
{ "receiveCalls": false }. A conexão reinicia de novo.
Passo 2 — Assine os eventos em tempo real
Abra um WebSocket em/voip/events. A autenticação vai na query string, porque
o navegador não define cabeçalhos em WebSocket.
string
required
Sua API Key. Aceita chave de usuário ou chave com escopo de sessão — nesse
caso, só as sessões dela passam.
string
required
Lista de
sessionId separados por vírgula. Valores repetidos e vazios são
descartados. Máximo de 20 sessões por conexão.unauthorized. Um cliente que
recebesse um subconjunto silenciosamente acharia que está ouvindo sessões que
nunca assinou.
Assim que a assinatura é aceita, o servidor envia o primeiro frame:
1008:
Sem replay de histórico
O socket entrega apenas os eventos que chegarem com ele já conectado. Não
há reentrega de backlog, e isso é intencional: uma oferta de chamada vale
poucos segundos e não é atendível depois disso. Reentregar o histórico faria o
seu app anunciar uma ligação que já acabou — junto com o
call.rejected dela.Formato dos frames
Todos os frames são texto JSON. Este canal é somente leitura: atender e recusar são chamadas HTTP, e o áudio vive no outro WebSocket.string
required
Um de
call.offer, call.accepted, call.ended ou call.rejected.string
required
Identificador da chamada. É o que você envia em
/calls/accept e
/calls/reject.string
JID de quem está ligando, já resolvido para o número de telefone quando
possível. Opcional.
string
JID original informado pelo bridge. Pode vir como
@lid. Opcional.number
required
Momento do evento em milissegundos (epoch). Quando o bridge não carimba o
evento, vale o horário de chegada no servidor.
string
required
Conexão que recebeu a chamada. Acrescentado pelo servidor, porque um socket
cobre várias sessões e o
accept exige o sessionId.Passo 3 — Atenda a chamada
POST /calls/accept com o sessionId e o callId que vieram no call.offer.
string
Identificador da chamada confirmado pelo bridge. Pode diferir do que você
enviou — use sempre o valor da resposta daqui em diante.
string
URL do WebSocket de mídia, com
callId e token já embutidos. Abra-a
exatamente como veio.string
Token de acesso àquela chamada (a sua API Key). Já está dentro da
wsUrl.POST /calls/stream. A partir daqui o
áudio, os eventos de ciclo de vida, o mute, o desligar e a gravação funcionam
exatamente como descrito em Ligações por Stream —
PCM s16le, 16 kHz, mono, frames de 320 amostras. Essa documentação não é
repetida aqui.
Erros possíveis:
Sessão inexistente ou fora do seu escopo.
Conexão da API Oficial (Cloud API), ou o bridge recusou o atendimento — a
oferta pode já ter expirado ou sido atendida em outro lugar. Nada é registrado
nesse caso: você não recebe uma
wsUrl que nunca entregaria áudio.O stream de voz está indisponível no momento.
Passo 4 — Recuse a chamada
POST /calls/reject precisa de três campos. Além do sessionId e do callId,
vai o from: o JID de quem ligou, exatamente como veio no call.offer.
Repasse o
from sem transformar. O WhatsApp rejeita contra aquele JID, não
contra um número normalizado — se o evento trouxe um @lid, envie o @lid.call.rejected correspondente é assíncrono: ele chega depois, pelo socket
de eventos e pelo webhook. A resposta 200 significa que o comando foi aceito
pelo bridge, não que a outra ponta já parou de tocar.
Exemplo completo
Um cliente Node que assina os eventos, atende a primeira oferta e conecta no WebSocket de mídia. A API Key nunca sai do servidor.atender por:
Próximos passos
Ligações por Stream
O WebSocket de mídia em detalhe: formato de áudio, ciclo de vida e gravação.
Webhooks de chamadas
A trilha server-to-server dos mesmos eventos, com histórico.
