# Chamadas de voz e vídeo Ligações usam uma instância de tipo `calls`, com pareamento próprio e áudio nos dois sentidos pelo navegador. O módulo precisa estar habilitado no servidor. Vídeo é opcional por cima do áudio, habilitado à parte e ainda experimental (veja [Chamadas de vídeo](#chamadas-de-vídeo)). ## Capacidade anunciada Consulte [GET /v1/capabilities](https://wpp.atendro.cloud/docs/api/get-capabilities.md) com `admintoken`. Exigir `capabilities.calls=true` e `instance_kinds` contendo `calls`. Este recorte ilustra um servidor habilitado: ```json { "capabilities": {"calls": true, "calls_per_instance_limit": true, "calls_video": false}, "instance_kinds": ["whatsapp", "calls"], "calls_max_concurrent": 1, "calls_max_concurrent_range": {"min": 1, "max": 8} } ``` Com `ATENDROZAP_CALLS_ENABLED=false`, criar ou operar a instância de ligação é recusado. O limite padrão é uma chamada por instância. Conectar uma instância de mensagens não habilita voz nela. `calls_video` anuncia o vídeo (`ATENDROZAP_CALLS_VIDEO_ENABLED=true`); sem ele, `video: true` e as rotas de vídeo respondem `501 calls_video_not_supported`. ## Limite de ligações por instância Escolha **Ligações simultâneas** ao criar uma instância de ligação ou abra **Instância → Visão geral → Ligações simultâneas → Salvar limite**. Aceita de 1 a 8, incluindo ligações ainda tocando. O único modo homologado em chamadas reais continua sendo 1 por vez. Pela API, informe `callsMaxConcurrent` em `POST /instance/init` com `kind: calls`. Para alterar depois, use [POST /instance/call-settings](https://wpp.atendro.cloud/docs/api/configure-instance-calls.md) com o header `token`, ou [POST /v1/instances/{id}/call-settings](https://wpp.atendro.cloud/docs/api/admin-configure-instance-calls.md) com `admintoken`: ```json {"callsMaxConcurrent": 2} ``` O limite é persistido e aparece no objeto `instance` do status e da listagem, junto de `callsConfigVersion` (inteiro crescente; inclusive `0` antes de uma edição). Voz e vídeo compartilham o mesmo limite; cada chamada `ringing`, `connecting` ou `active`, recebida ou realizada, ocupa uma vaga. Pode ser salvo com a instância desconectada. Alterar não reconecta a sessão: ligações em andamento são preservadas, mesmo que ultrapassem um limite reduzido; novas ligações aguardam haver vaga e recebem `409 line_busy` enquanto não houver. O worker local aplica imediatamente. Se a sessão estiver em outro worker, ele aplica na próxima renovação de lease e antes de cada nova ligação de saída. `calls_max_concurrent` em capacidades é o padrão do servidor, não o limite individual. Instâncias antigas sem escolha própria continuam usando esse padrão até a primeira edição; novas instâncias persistem o padrão da criação. Configure os eventos `call`, `call_settings` e `connection` na aba **Webhook** da própria instância. A inscrição também pode ser feita por API, conforme o [guia de webhooks](https://wpp.atendro.cloud/docs/guias/webhooks.md). Cada `POST /instance/call-settings` ou variante administrativa bem-sucedido incrementa `callsConfigVersion` e grava um evento `call_settings` na mesma transação, mesmo quando o valor enviado é igual ao anterior ou a instância está desconectada. O evento é entregue somente com inscrição explícita em `call_settings`; mudar o limite não altera a inscrição do webhook. O novo evento mantém o envelope assinado existente, com `data: {kind: "calls", callsMaxConcurrent, callsConfigVersion, updatedAt}`. `updatedAt` usa UTC em RFC3339Nano; nos objetos HTTP o campo de data existente continua se chamando `updated`. `capabilities.calls_settings_webhook=true` anuncia esse contrato. Eventos `connection` de uma instância `calls` também acrescentam `instance.kind` e, quando o banco pode ser consultado, `instance.callsMaxConcurrent` e `instance.callsConfigVersion`. Esses dois últimos campos são opcionais: a ausência não redefine o limite. Assinar o webhook não envia um snapshot inicial; consulte `GET /instance/status` ao integrar ou retomar a instância e compare a versão antes de aplicar respostas HTTP ou eventos, evitando regressões. Uma instância `calls` gera eventos de chamada (`call`), configuração (`call_settings`) e ciclo de vida (`connection`, `limits`, conforme inscrição). Ela não persiste nem publica mensagens, recibos, edições, eventos de grupos, histórico de mensagens ou `undecryptable` recebidos nessa sessão. Uma instância `whatsapp` separada continua responsável pelas conversas. O [prompt de integração com o Atendro](https://wpp.atendro.cloud/docs/guias/atendro-ligacoes-simultaneas.md) detalha sincronização, vagas, reservas e testes. As adições de configuração e o filtro de mensagens descritos aqui aguardam publicação; não presumir que uma API em produção já os anuncia. ## Primeiro teste pelo painel 1. Abra o servidor no [console](https://wpp.atendro.cloud) e clique em **Nova instância**. 2. Selecione **Tipo → Ligação**, informe um nome e crie. O painel guarda o token no backend. 3. Solicite a conexão e escaneie o QR pelo WhatsApp do número autorizado. Aguarde **connected**. Pode ser outro dispositivo vinculado do mesmo número usado para mensagens ou um número dedicado. 4. Nas **Conversas** da instância de mensagens, abra a conversa autorizada e clique em **Ligar**. O painel escolhe uma instância de ligação conectada do mesmo servidor cujo token conhece. Também é possível usar **Discar** na página da instância de ligação. 5. Permita o microfone, atenda no telefone de destino e confira áudio nos dois sentidos. O widget segue a tela de ligação do WhatsApp: o contato no palco, os controles redondos embaixo. **Mudo** controla o microfone; **Câmera** liga o vídeo (quando habilitado); **Gravando** mostra a gravação automática e, ao encerrar, vira **Gravação**, que abre o player do áudio ali mesmo; **Desligar** encerra a chamada. Mantenha a página aberta durante a conversa. Se o tipo **Ligação** não aparecer, confira a capacidade do servidor. Se o painel não conhecer o token de uma instância criada fora dele, reemita-o pela interface antes do uso. Erros de linha ocupada ou sessão desconectada são mostrados no widget. ## Ruído e eco O painel solicita cancelamento de eco, supressão de ruído e ajuste de ganho ao navegador. Quando o dispositivo oferece o modo de cancelamento de todo o áudio reproduzido, ele é preferido para incluir a voz que sai pelo painel. O widget avisa quando não consegue confirmar o cancelamento de eco. **Reduzir ruído do microfone** vem marcado: acrescenta um filtro de graves e atenua suavemente o fundo entre as falas. Desmarque durante uma chamada para comparar, especialmente se sua voz for muito baixa. Essa opção controla o filtro adicional; o cancelamento de eco do navegador continua solicitado. O tratamento ocorre no computador do atendente antes do envio e não altera o som recebido. Quando a gravação automática está habilitada no servidor, ela captura a voz já tratada do atendente e a voz recebida do contato. O filtro não elimina todas as vozes, teclas ou sons fortes enquanto você fala. Use fone se persistir eco; no teste com dois aparelhos próximos, afaste o telefone do microfone do computador e evite volume alto no viva-voz. A qualidade depende também do navegador, microfone e ambiente. ## Rotas e estados | Operação | Resultado | |---|---| | [POST /call/make](https://wpp.atendro.cloud/docs/api/make-call.md) | Corpo `{number, video?}`; `201` com `callId`; aceita `Idempotency-Key` | | [POST /call/answer](https://wpp.atendro.cloud/docs/api/answer-call.md) | Corpo `{callId}`; atende uma chamada recebida | | [POST /call/reject](https://wpp.atendro.cloud/docs/api/reject-call.md) | Corpo `{callId}`; recusa uma chamada recebida | | [POST /call/hangup](https://wpp.atendro.cloud/docs/api/hangup-call.md) | Corpo `{callId}`; encerra a chamada | | [POST /call/video](https://wpp.atendro.cloud/docs/api/call-video.md) | Corpo `{callId, action}` com `start`, `accept` ou `stop`; liga, aceita ou desliga o vídeo | | [GET /call/active](https://wpp.atendro.cloud/docs/api/list-active-calls.md) | Lista as chamadas vivas da instância | | [GET /call/{id}](https://wpp.atendro.cloud/docs/api/get-call.md) | Estado da chamada viva ou do histórico persistente | | [GET /call/history](https://wpp.atendro.cloud/docs/api/list-call-history.md) | Histórico paginado, com filtros e situação da gravação | | [GET /call/{id}/recording](https://wpp.atendro.cloud/docs/api/download-call-recording.md) | Reprodução/download autenticado do WAV, com suporte a Range | | [GET /call/{id}/audio](https://wpp.atendro.cloud/docs/api/stream-call-audio.md) | Upgrade WebSocket; PCM s16le mono a 16 kHz | | [GET /call/{id}/video](https://wpp.atendro.cloud/docs/api/stream-call-video.md) | Upgrade WebSocket; H.264 Annex-B com cabeçalho de 8 bytes e avisos JSON | Todas usam o token da instância `calls`. Estados: `ringing`, `connecting`, `active`, `ended`; o encerramento inclui `endReason` e `endedBy`. Uma instância `whatsapp` mantém `501 calls_not_supported` em `/call/make`; outras rotas de ligação recusam o tipo incorreto com `409 wrong_instance_kind`. O áudio passa pelo console, que autentica a sessão do navegador e adiciona o token no pedido à API. Apenas as pontes de áudio e vídeo aceitam `?token=` como alternativa ao header. Prefira o proxy para manter a credencial no backend e não registrar tokens em URLs. A perda do socket de áudio ou vídeo abre uma janela de 30 segundos para reconexão com o mesmo callId. Reabrir o socket do mesmo tipo cancela seu prazo; se ele não retornar, a chamada termina com hangup/local. A negociação de vídeo é preservada durante a janela e a nova ponte pede um keyframe. Para desligar voluntariamente, use POST /call/hangup; para desligar só a câmera, use POST /call/video com action: stop. ## Chamadas de vídeo Recurso experimental. O motor de chamadas transporta o vídeo (H.264 pelo relay do WhatsApp) e a sinalização de upgrade foi validada pelo projeto upstream contra uma segunda implementação, mas o caminho de mídia de vídeo ainda não foi homologado com o número de laboratório. Por isso o vídeo fica atrás de `ATENDROZAP_CALLS_VIDEO_ENABLED` e é anunciado em `capabilities.calls_video`. Três formas de haver vídeo numa chamada: 1. **Chamada de vídeo desde o toque**: `POST /call/make` com `{"number": "...", "video": true}`. A oferta anuncia vídeo e `videoState.sending` já vem verdadeiro. 2. **Upgrade no meio da chamada**: `POST /call/video` com `{"callId": "...", "action": "start"}` numa chamada atendida. Fica `videoState.pending` até o contato aceitar no telefone; aceite abre o envio (`sending`), recusa devolve ao áudio. Numa chamada que já negociou vídeo, `start` apenas religa a câmera. 3. **Pedido do contato**: o telefone pede vídeo e chega `data.event: "video"` com `videoState.requested`. Responda com `action: "accept"`; ignorar deixa o WhatsApp retirar o pedido. Uma chamada recebida como vídeo já nasce com os dois fluxos anunciados. `action: "stop"` desliga a câmera deste lado (ou retira o pedido pendente) sem mexer no áudio nem no vídeo do contato. `videoState.receiving` diz se o contato está com a câmera ligada e `orientation` (0–3, quartos de volta) como girar os quadros dele. Os quadros trafegam por `GET /call/{id}/video`, um WebSocket separado do áudio. Cada mensagem binária é um cabeçalho de 8 bytes (versão 1; flags com bit 0 = keyframe; orientação; reservado; duração do quadro em microssegundos, big-endian) seguido de uma unidade de acesso H.264 em Annex-B. A API não codifica nem decodifica pixels: quem envia produz H.264 (WebCodecs no navegador, ffmpeg, codec de hardware) e quem recebe decodifica. Abra o socket junto com a chamada, antes de haver vídeo, para não perder o keyframe que abre o fluxo do contato; quadros enviados com o fluxo fechado são descartados, e enquanto o motor espera um keyframe os quadros intermediários também. Mensagens de texto da API são avisos JSON: `{"event":"keyframe"}` pede um IDR no próximo quadro e `{"event":"video","video":{...}}` acompanha cada mudança de `videoState` (o primeiro aviso é o estado atual). Códigos de fechamento: 4000 chamada encerrada, 4001 cabeçalho inválido, 4002 falha de escrita, 4003 substituído por outro socket. No painel, com a capacidade anunciada e um navegador com WebCodecs (Chrome, Edge, Safari 16.4+, Firefox 130+), o discador da instância de ligação ganha **Chamada de vídeo**, as conversas ganham **Vídeo**, e **Câmera** no widget faz o upgrade. O vídeo do contato ocupa o palco do widget e a própria câmera aparece como miniatura, como na tela de chamada do WhatsApp. Resolução enviada: até 640×480 a 15 quadros por segundo, cerca de 0,6 Mbit/s por sentido — o servidor só repassa os bytes, sem transcodificar. O histórico marca `video: true` nas chamadas que tiveram vídeo. Com `capabilities.calls_video_recording=true`, a captura opcional também gera um MP4 com as duas câmeras lado a lado após o encerramento. O WAV continua independente; `videoRecording` informa o processamento e o download. Veja [Gravação de videochamadas](https://wpp.atendro.cloud/docs/guias/captura-video.md) para habilitação, limites e integração no Atendro. A gravação de vídeo permanece desligada por padrão e ainda exige homologação em celulares reais. ## Registrar o atendimento no sistema parceiro Inscreva `call` no webhook global ou próprio para receber `data` com `callId`, direção, estado e motivo de encerramento. A entrega segue a política de assinatura, retry e deduplicação dos demais eventos. O sinal de atendimento é **`EventType: "call"` e `data.event: "answered"`**. Nesse momento `data.answered` é `true`, `data.answeredAt` traz o horário do aceite e `data.status` é `connecting`. O estado `active` chega depois, quando o motor recebe áudio; não espere por ele para registrar que atendeu. Isso vale para o aceite remoto de uma chamada realizada e para o atendimento local de uma chamada recebida. `ringing` e `preaccept` não são atendimento. ```json { "EventType": "call", "event": "call", "eventId": "ID_DO_EVENTO", "instanceId": "ID_DA_INSTANCIA", "seq": 2, "data": { "callId": "ID_DA_CHAMADA", "event": "answered", "direction": "outbound", "status": "connecting", "answered": true, "startedAt": "2026-09-18T15:00:00Z", "answeredAt": "2026-09-18T15:00:05Z" } } ``` O exemplo é um recorte: o envelope completo inclui as identidades previstas no contrato. No consumidor, valide a assinatura sobre o corpo original, identifique a instância por `instanceId` e faça upsert por `(instanceId, data.callId)`. Deduplicate por `eventId`; retries preservam esse identificador. Use `seq` do envelope para ordenar, pois `data.seq` pertence à sessão de ligações. Responda 2xx após guardar o evento. Todo evento traz `video` (booleano) e, enquanto a chamada vive, `videoState`. `data.event: "video"` marca mudanças só de vídeo (pedido do contato, aceite, câmera ligada/desligada), sem alterar `status`. `data.event: "active"` acrescenta `mediaStartedAt`, mantendo `answeredAt`. `data.event: "ended"` acrescenta `endedAt`, `endReason`, `endedBy` e `durationSeconds`. Os eventos posteriores também preservam `answered=true` e `answeredAt`, permitindo reconciliar o atendimento se o consumidor perdeu uma notificação. Chamadas não atendidas terminam com `answered=false`. `endReason: "hangup"` com `endedBy: "remote"` também representa um nó WhatsApp `terminate` remoto sem motivo explícito (ausente, vazio ou só espaços). Indica solicitação remota de encerramento; isoladamente não comprova que alguém apertou o botão de desligar. Motivos explícitos são preservados; um callback genérico sem motivo continua `unknown`. O primeiro motivo fica no evento e no histórico, sem reclassificar chamadas antigas. `data.event: "recording_ready"` informa `callId` e `recording` com `status`, `bytes` e `durationMs` depois de o arquivo ficar pronto. `recording_failed` informa falha, sem inventar um áudio disponível. Esses dois eventos atualizam a gravação, não o estado da chamada; não dependem de um campo `status` da chamada e podem chegar antes do evento de encerramento. `data.event: "video_recording_ready"` e `"video_recording_failed"` informam `callId` e o objeto separado `videoRecording`, com `contentType: "video/mp4"`, `status`, `bytes`, `durationMs` e `failureReason`. São atualizações da gravação de vídeo: não são `ended`, não mudam vagas de simultaneidade e não substituem `recording` do WAV. Consulte o [guia de gravação de vídeo](https://wpp.atendro.cloud/docs/guias/captura-video.md). ## Histórico e gravação automática No painel, abra a instância de ligação e a aba **Histórico de chamadas**. É possível filtrar realizadas/recebidas e atendidas/sem atendimento, consultar duração, ouvir e baixar. O histórico permanece após reiniciar a API e após a expiração do áudio; não depende da retenção de `/v1/events`. Na API, `GET /call/history?limit=25&answered=true` lista a primeira página. Envie o `nextCursor` recebido como `before` para buscar chamadas mais antigas. Filtros opcionais: `direction`, `status`, `from` (inclusivo) e `to` (exclusivo), com datas RFC3339. Máximo de 100 itens por página. Na VPS 1 a configuração solicitada é gravar **todas as chamadas atendidas** e reter o áudio por **30 dias**. `capabilities.call_history` e `capabilities.call_recording` anunciam os recursos; a retenção está em `calls_recording_retention_days`. A captura começa no aceite, sem gravar toques, e acompanha substituições da ponte de áudio. O WAV tem PCM 16-bit, 16 kHz, dois canais: atendente à esquerda e contato à direita. O som processado ocupa aproximadamente 3,84 MB por minuto de chamada. Cada item tem `recording.status`: `not_recorded`, `recording` (captura ou finalização), `ready`, `failed` ou `expired`. Quando pronto, `downloadPath` aponta para `GET /call/{id}/recording`. **Use o header `token` no backend**: essa rota não aceita credencial na query. Faça proxy autenticado para o player do Atendro, repassando `Range` para permitir avançar no áudio. O console já faz isso sem expor o token ao navegador. Depois do prazo, o download responde `410 recording_expired`; a limpeza dos arquivos roda de hora em hora, preservando o histórico. Um worker reiniciado durante uma chamada marca o registro como encerrado por `worker_restarted` e a gravação incompleta como falha. Disco indisponível ou fila de gravação esgotada também gera falha explícita, sem bloquear a chamada. Os eventos antigos ainda existentes são importados para o histórico pela migração. Não há áudio recuperável de chamadas anteriores à ativação. ## Comportamento ao receber uma ligação Servidores com `capabilities.calls_incoming_policy=true` aceitam `incomingCallMode` em `POST /instance/call-settings` (ou na variante administrativa por ID). A atualização é parcial: alterar apenas o modo preserva o limite, e alterar apenas o limite preserva o modo. | Modo | Resultado | |---|---| | `allow` | A chamada fica disponível para um atendente aceitar. É o padrão das instâncias existentes. | | `reject` | O servidor recusa a chamada recebida, mesmo com o navegador fechado. | | `reject_reply` | O servidor recusa e publica a solicitação de resposta automática para o consumidor. | O modo e `callsConfigVersion` são congelados na chegada da chamada. A mudança vale para chamadas novas, sem interromper atendimentos em andamento. Cada atualização aceita publica o snapshot completo em `call_settings`. Para impedir que duas edições concorrentes sobrescrevam uma à outra, envie `expectedCallsConfigVersion`; divergência retorna `409 settings_version_conflict`. ```json { "incomingCallMode": "reject_reply", "expectedCallsConfigVersion": 3 } ``` A instância `calls` não envia mensagens. No Atendro-AI, configure explicitamente o texto e uma conexão de mensagens da mesma empresa e do mesmo número. A recusa e o envio são operações independentes: o histórico precisa mostrar resposta pendente, enviada ou falha, sem afirmar que recusar comprovou o envio. Os snapshots/eventos de chamada recebida incluem `incomingCallMode` e `callsConfigVersion`. Uma recusa confirmada inclui `rejectionSource` (`policy`, `busy` ou `manual`). `autoReplyRequested=true` aparece apenas após recusa bem-sucedida por política `reject_reply`; não envie texto a partir de qualquer evento `ended/rejected`. Chamadas rejeitadas por ocupação também geram histórico/eventos, sem ocupar uma vaga adicional. O consumidor deve deduplicar por servidor, instância e chamada, usar a configuração da versão recebida e limitar a frequência de resposta por contato. Eventos repetidos, atrasados e reprocessados não autorizam uma segunda mensagem. Falta de identidade resolvida ou de conexão válida gera falha explícita. Uma chamada não atendida expira localmente: isso não equivale a recusa nem autoriza responder. A recusa explícita pode afetar o toque em outros aparelhos vinculados à conta. ## Limites de validação A bancada validou uma chamada curta de saída com áudio nos dois sentidos, e o responsável confirmou a chamada real pelo painel da VPS em 18/09/2026. Após o ajuste de áudio, repetiu a chamada e confirmou melhora tanto do ruído quanto do eco. Essa confirmação é qualitativa, no ambiente testado. Latência boca-ouvido medida, chamada longa, recebimento e o defeito upstream #25 (recusa por aparelho Web/Desktop do destino) continuam pendentes de homologação. Veja [validação e limites](https://wpp.atendro.cloud/docs/guias/validacao.md).