Atendro: ligações simultâneas
Prompt de implementação: limite versionado por webhook, vagas e reservas entre atendentes.
Este texto pode ser enviado ao agente que trabalha no repositório atendro-ai. O contrato abaixo foi preparado no AtendroZAP, mas ainda não foi publicado na VPS 1. A implementação no consumidor pode avançar com fixtures; o teste real depende de capabilities.calls_settings_webhook=true no servidor escolhido. Nenhuma mudança de instâncias, webhooks de produção ou limite em produção é autorizada por este documento.
Texto para implementar no atendro-ai
Implemente a sincronização do limite de ligações simultâneas por instância AtendroZAP e a gestão das vagas no backend e na interface do Atendro. Inspecione primeiro os módulos atuais de provedor, conexões, webhooks, chamadas, ponte de mídia e permissões. Reutilize os mecanismos de inbox, assinatura e reconexão já existentes; preserve os demais provedores e os contratos em uso.
O usuário precisa conseguir configurar uma instância para duas ligações, ver o limite atualizado quando ele mudar no painel do AtendroZAP e distribuir as duas vagas entre atendentes. Voz e vídeo compartilham o limite. Isso é uma configuração de admissão do provedor: mais de uma ligação simultânea no mesmo número ainda não foi homologada em aparelhos reais. Não apresente o número configurado como capacidade física comprovada ou como duas linhas WhatsApp independentes.
Contrato do provedor
Use a URL do servidor vinculado à conexão, nunca uma VPS fixa no código. Credenciais ficam no backend. As rotas de instância recebem header token; rotas administrativas usam admintoken, acessível somente a administradores autorizados. Não exponha esses segredos em bundle, navegador, URL, log ou erro.
GET /v1/capabilitiesanunciacapabilities.calls,capabilities.calls_per_instance_limitecapabilities.calls_settings_webhook;calls_max_concurrent_rangecontém{min: 1, max: 8}.calls_max_concurrenté o padrão do servidor, não o limite salvo da instância. Para vídeo, exija tambémcapabilities.calls_video.GET /instance/statusretornainstance.kind,instance.callsMaxConcurrenteinstance.callsConfigVersionparakind: calls. A versão é um inteiro de 64 bits, inclusive0; não confunda zero com ausência. O campo de data HTTP existente éinstance.updated, nãoupdatedAt.POST /instance/call-settings, com headertoken, recebe{"callsMaxConcurrent": 2}. A variante administrativa éPOST /v1/instances/{id}/call-settings. A resposta contém o objetoinstancecom limite e versão canônicos. Cada POST aceito incrementa a versão, mesmo repetindo o mesmo valor. Não existe comparação condicional de versão nesta rota: escritas concorrentes vencem pela última versão salva.- O limite de 1 a 8 conta chamadas recebidas e realizadas, de voz ou vídeo, em
ringing,connectingeactive, além das discagens que o serviço já reservou. Uma chamada de vídeo ocupa uma vaga, mesmo usando dois sockets. - Reduzir o limite preserva chamadas vivas. Se houver duas e o limite cair para um, nenhuma é encerrada; novas chamadas só entram quando o total ficar abaixo de um. Sem vaga, uma discagem recebe
409 line_busy. A API não cria uma fila de discagem automática; não repita chamadas em loop ao liberar vaga. - A alteração pode ser salva desconectada. O worker local aplica sem reconectar; outro worker aplica na próxima renovação de lease e antes de cada nova discagem de saída. Uma confirmação de configuração não comprova que uma ligação de teste foi realizada.
GET /call/activeretorna{calls: [...]}para a instância. A lista não mostra reservas de discagem que ainda não receberamcallId.GET /call/{id}eGET /call/historypermitem reconciliar chamadas e fins.
Inscrição e processamento dos webhooks
No receptor HMAC existente, acrescente call_settings à lista de eventos reconhecidos. Uma instância calls deve assinar call, call_settings e connection; limits é opcional conforme a integração. Ela não publica mensagens, recibos, edições, grupos ou undecryptable. call_settings não é um subevento call e não contém callId: nunca crie uma chamada a partir dele.
Ao preparar a inscrição, consulte GET /webhook e mescle os nomes necessários com os eventos já salvos. Preserve URL, segredo, filtros, estado habilitado e demais opções. Para a mesma URL, omitir secret preserva o segredo existente; não envie segredo vazio ou inventado. O GET não devolve o segredo. Se o cadastro for herdado do global, preserve a herança: alteração global exige permissão administrativa e avaliação das demais instâncias. Não transforme silenciosamente um webhook herdado em exceção própria nem substitua configurações de clientes. Se a inscrição estiver desabilitada ou ausente, siga o fluxo autorizado de provisionamento do produto, sem ativá-la como efeito colateral de mudar o limite.
O AtendroZAP grava a configuração e o evento atomicamente. Inscrição ausente não impede salvar o limite, mas não há entrega sem assinar call_settings. Registrar o webhook não produz snapshot inicial. Depois de preparar a inscrição, leia o status para obter o estado atual e resolver a corrida com eventos recebidos durante essa leitura.
Exemplo sintético parcial do novo evento, no envelope assinado atual:
{
"EventType": "call_settings",
"event": "call_settings",
"eventId": "00000000-0000-4000-8000-000000000002",
"instanceId": "00000000-0000-4000-8000-000000000001",
"seq": 42,
"data": {
"kind": "calls",
"callsMaxConcurrent": 2,
"callsConfigVersion": 3,
"updatedAt": "2026-09-19T15:00:00.123456789Z"
}
}updatedAt é UTC em RFC3339Nano. O evento connection mantém seu formato e acrescenta instance.kind: calls; quando a leitura do banco é possível, também traz instance.callsMaxConcurrent e instance.callsConfigVersion. Esses campos opcionais em connection não ficam em data. Se ausentes, preserve o limite conhecido e reconcilie pelo status.
Valide a assinatura HMAC-SHA256 sobre timestamp + "." + corpo bruto, a janela de timestamp e o vínculo servidor/instância/empresa antes de aceitar. Deduplique por identidade do servidor, instanceId e eventId; confirme 2xx somente após persistência durável. Retries preservam o eventId. Use seq do envelope para ordenar eventos; a versão de configuração é um contador separado.
Persista limite e versão juntos. Aplique snapshots de resposta POST, GET, call_settings e connection pela mesma regra transacional: versão maior substitui, menor é ignorada, igual é idempotente. Uma mesma versão com valor divergente exige reconciliação, sem escolher pelo horário. Campos ausentes em versões antigas da API não equivalem a zero. Não use updatedAt, horário de chegada ou ordem das promessas HTTP para sobrescrever uma versão maior. Use representação que preserve inteiros de 64 bits nos componentes que precisarem deles; não faça comparação textual lexicográfica de versões.
Após um POST, aplique a resposta canônica e reconcilie com GET. Se houver timeout ou perda de resposta, leia o status antes de repetir a alteração. Notifique as sessões do Atendro vinculadas à conexão após o commit, usando o canal de atualização que o produto já possui. Uma mudança feita fora do Atendro deve aparecer sem exigir novo pareamento ou recarregar a página.
Gestão de vagas e operação pelos atendentes
Mantenha a identidade da chamada como (servidor, instanceId, callId); no escopo de um servidor, (instanceId, callId). Um mesmo callId pode existir em duas instâncias quando ambas as pontas estão pareadas. Conte recebidas e realizadas, incluindo as que estão tocando. answered confirma atendimento em connecting; não espere active para reservar a vaga.
Eventos video atualizam a mesma chamada e não consomem vaga adicional. Eventos repetidos ou fora de ordem não podem incrementar o contador de novo. ended libera uma vez e conserva o primeiro motivo/origem de encerramento; eventos antigos não reabrem a chamada. recording_ready e recording_failed só atualizam a gravação: não ocupam nem liberam uma vaga por conta própria. Evite um contador isolado de incrementos/decrementos; derive a ocupação de registros idempotentes de chamadas e reservas válidas.
Antes de discar, faça reserva atômica da vaga no backend compartilhado do Atendro. Duas abas, atendentes ou réplicas podem discar ao mesmo tempo; desabilitar o botão no navegador não impede essa disputa. Conte reservas pendentes ainda sem callId, atribua identidade à tentativa e reutilize sua Idempotency-Key ao repetir a mesma discagem. Ao receber callId, converta a reserva em chamada sem contar duas vezes. Correlacione também quando o webhook chegar antes da resposta HTTP. Em falha definitiva ou line_busy, libere a reserva e reconcilie. Em resultado ambíguo, preserve-a de forma conservadora até resolver pelo mecanismo de idempotência e consulta do provedor; o GET de chamadas ativas não mostra reservas internas ainda sem ID. Não libere uma reserva apenas porque o navegador fechou ou uma resposta demorou.
O provedor continua sendo a autoridade final de admissão: ligações recebidas podem ocupar uma vaga entre a checagem local e a discagem. Trate 409 line_busy como condição operacional, atualize a ocupação e informe o atendente. Quando sem vaga, mostre o limite e as chamadas que a ocupam. Se o Atendro implementar espera, ela deve ser explícita e cancelável; não iniciar uma ligação futura sem a intenção do atendente ainda válida.
Mostre, por conexão, limite configurado, chamadas vivas e tentativas pendentes. Disponibilidade é no mínimo zero; ao reduzir abaixo da ocupação, mostre a situação excedente sem desligar ninguém. A permissão de alterar o limite deve ser validada no backend, respeitar empresa e conexão e aceitar somente inteiros de 1 a 8. Estado desconectado bloqueia discagem, mas permite configurar o limite.
Cada chamada precisa de proprietário/atendente e ponte de mídia próprios, identificados por instância e callId. Não use um único socket ou estado global por número/conexão. Dois atendentes não devem tomar a mesma chamada por acidente; o AtendroZAP aceita substituir um attachment, o que não é um bloqueio de propriedade de atendimento. Preserve as regras existentes de transferência. Microfone, reprodução e câmera devem ser associados à chamada atendida e ao usuário correto, sem misturar áudio entre chamadas.
Mantenha a janela existente de reconexão da ponte: o AtendroZAP tolera 30 s sem socket de áudio/vídeo e o Atendro retém a sessão por 25 s. Desligamento voluntário usa /call/hangup; câmera desligada usa /call/video com action: stop e não libera a vaga. Uma queda de socket não é, sozinha, prova de ended.
Ao abrir/reconectar a tela, ao reconectar o receptor e após erros de admissão, reconcilie configuração e chamadas com GET /instance/status e GET /call/active. Consulte o estado/histórico das chamadas locais que desapareceram da lista para obter o encerramento real. Proteja essa leitura contra eventos mais novos recebidos em paralelo; preserve as reservas ainda em resolução. Faça reconciliação periódica moderada como recuperação de eventos perdidos, sem depender de polling rápido para a operação normal.
Testes e entrega
Cubra com testes significativos, usando mocks/fixtures e banco descartável:
- Limite alterado no AtendroZAP de 1 para 2 chega via webhook e atualiza persistência, interface e admissão sem reconectar a instância.
- Configuração via Atendro usa credencial no backend, valida permissões, aceita 1 a 8 e rejeita valores inválidos; falha remota não aparenta sucesso.
- Snapshot inicial, versão zero, HTTP antigo após webhook novo, eventos duplicados/fora de ordem, assinatura inválida e instância de outra empresa.
- Duas reservas concorrentes com limite 1 admitem apenas uma; com limite 2 admitem duas e recusam a terceira, inclusive entre réplicas do backend.
- Webhook chega antes da resposta de discagem; timeout ambíguo não duplica chamada nem libera a vaga prematuramente;
409 line_busyreconcilia. - Chamada recebida disputa vaga com saída; vídeo e gravação não contam duas vezes; duas instâncias com o mesmo
callIdcontinuam separadas. - Redução de 2 para 1 com duas chamadas vivas preserva ambas e bloqueia novas.
- Reload, queda do receptor, reconexão dos sockets e atualização concorrente preservam propriedade, contagem e primeiro encerramento.
- Inscrição mescla eventos sem perder URL, segredo, filtros ou herança; instância de mensagens e demais provedores conservam o comportamento atual.
Entregue mudanças, migrações necessárias, testes executados e instruções de ativação. Não publique nem altere instâncias, limites, webhooks ou credenciais de produção apenas para executar testes. Separe o resultado dos testes locais da homologação pendente de duas chamadas WhatsApp reais no mesmo número.
Referências: Chamadas, Eventos, Receptor HMAC e OpenAPI.