Implementar o receptor
Corpo bruto, assinatura HMAC, deduplicação e vínculo com a empresa.
Contrato para criar um novo receptor específico do AtendroZAP. O endpoint do provedor anterior não é reutilizado. A implementação e a URL definitiva no Atendro serão feitas separadamente; nenhum destino real está predefinido aqui.
Cadastro global por servidor
No console wpp.atendro.cloud: Servidores → servidor → Configurações de webhook. Informe URL HTTPS pública, segredo de assinatura e eventos. Salve com entrega habilitada quando o receptor estiver pronto. Credenciais reais ficam no backend privado.
Via API, GET /v1/webhook consulta e POST /v1/webhook salva na URL do servidor selecionado, ambos com header admintoken. A documentação fica na central; o webhook global continua sendo uma configuração de cada servidor. O corpo de cadastro é:
{
"url": "https://seu-atendro.example/webhooks/atendrozap",
"secret": "<SEGREDO_ALEATORIO_COMPARTILHADO>",
"events": ["connection", "messages", "messages_update", "messages_edit", "groups", "limits", "undecryptable"],
"enabled": true,
"excludeMessages": []
}O segredo deve ter até 256 caracteres ASCII sem espaços. Para gerar um novo, openssl rand -hex 32 produz 32 bytes aleatórios; guarde o resultado somente no servidor e no receptor. É diferente do admintoken e dos tokens de instância. Com ATENDROZAP_ENVELOPE_KEY, fica cifrado no banco; as consultas só devolvem hasSecret e secretSealed, nunca o valor.
| Ação | Resultado |
|---|---|
| Salvar o global | Aplica às instâncias atuais sem exceção e às novas, em transação |
Salvar enabled:false | Pausa as instâncias que herdam; a mesma URL mantém o cursor |
Salvar a mesma URL sem secret | Preserva o segredo anterior |
| Trocar URL | Começa após os eventos já existentes; informe o segredo do novo destino |
POST /v1/webhook com {"action":"delete"} | Remove o global e as inscrições herdadas; preserva as próprias |
POST /webhook com token de instância | Cria uma configuração própria, independente do global |
POST /webhook com {"action":"delete"} | Desativa essa instância inclusive em futuras mudanças globais |
POST /webhook com {"action":"inherit"} | Remove a exceção e passa a seguir o global, inclusive se cadastrado depois |
A administração por ID usa GET/POST /v1/instances/{id}/webhook e GET /v1/instances/{id}/webhook/errors, com admintoken, sem reemitir o token. GET /webhook informa inheritedGlobal quando há inscrição. Uma instância sem destino pode optar por inherit antes de existir um global. Se não existir global, a ação remove o destino próprio e aguarda o futuro cadastro.
Inscrições existentes antes desta versão são preservadas como configurações próprias. Para incluí-las no global, selecione Usar webhook global em cada uma. ATENDROZAP_WEBHOOK_URL e ATENDROZAP_WEBHOOK_SECRET inicializam o cadastro global uma única vez. A configuração salva no banco, inclusive sua remoção, prevalece sobre essas variáveis nos próximos reinícios.
Identificar servidor, instância e empresa
No Atendro, cadastre o servidor com URL base e token administrativo. Associe cada instanceId retornado por /instance/init ao servidor e à empresa autorizada. Use um segredo diferente por servidor e identifique o servidor pela rota/configuração do receptor, por exemplo /webhooks/atendrozap/{serverId}. Esse serverId é uma referência de cadastro do Atendro, não uma credencial.
Não resolva permissões por instanceName, owner, nome de empresa ou número. Depois da assinatura, confira se o instanceId pertence àquele servidor e a uma empresa ativa. O envelope não contém um serverId autenticado nem companyId. Não compartilhe um segredo global entre empresas sem esse vínculo no receptor.
Verificação do POST
O servidor envia JSON com timeout de 10 segundos, sem seguir redirecionamentos.
| Header | Conteúdo |
|---|---|
X-AtendroZAP-Event-Id | UUID do evento; igual a eventId |
X-AtendroZAP-Instance-Id | UUID da instância; igual a instanceId |
X-AtendroZAP-Event-Type | Tipo projetado; igual a EventType |
X-AtendroZAP-Event-Seq | Sequência numérica; igual a seq |
X-AtendroZAP-Attempt | Tentativa; informativo, não faz parte da assinatura |
X-AtendroZAP-Timestamp | Unix em segundos, gerado a cada tentativa |
X-AtendroZAP-Signature | sha256= seguido de HMAC SHA-256 em hexadecimal |
Assinatura: HMAC-SHA256(segredo, bytes(timestamp + ".") + bytes(corpo_bruto)). Verifique antes de chamar um parser que altere o corpo. Rejeite timestamps mais de 5 minutos no passado ou no futuro. A comparação deve usar uma primitiva criptográfica, como crypto.subtle.verify. Rejeite eventos sem assinatura.
verify-webhook.mjs implementa essa verificação, limita o corpo a 2 MiB e confere a correspondência dos headers com os campos assinados. É um módulo JavaScript sem dependências, importável também em uma Edge Function com Web Crypto. Os testes estão em scripts/verify_webhook_test.mjs.
O módulo não grava eventos, resolve empresas ou retorna sucesso HTTP. O receptor do Atendro deve completar esse fluxo:
- Resolver o servidor cadastrado a partir da rota e carregar seu segredo.
- Chamar
verifyAtendroZAP(request, secret)com o corpo ainda intacto. - Verificar o vínculo servidor → instância → empresa.
- Gravar o envelope em uma inbox durável com restrição única em
(server_id, instance_id, event_id). Uma duplicata já gravada pode receber 2xx. - Responder 204/200 apenas depois do commit. Em falha temporária do banco, responder 503 para que o AtendroZAP tente novamente.
- Processar a inbox com retry interno; marcar o processamento concluído na mesma transação das alterações do Atendro ou usando operações idempotentes.
Se a plataforma exigir JWT por padrão na entrada, ajuste somente essa função para aceitar os headers HMAC deste contrato. O AtendroZAP não envia Bearer/JWT, apikey da plataforma nem headers customizados. A assinatura e o vínculo de instância são a autenticação e autorização do novo receptor.
Eventos a tratar
O envelope comum contém eventId, seq, EventType, instanceId e instanceName. instance pode ser string ou objeto conforme o evento; owner pode estar ausente. Use instanceId como identificador estável.
| Tipo | Tratamento no Atendro |
|---|---|
connection | Ler state/status/connection e atualizar conexão. instance é objeto. QR não é enviado: consultar status autenticado |
messages | Upsert de mensagem pelo ID externo; distinguir texto, mídia, contato, localização e ReactionMessage |
messages_update | Processar todos os event.MessageIDs; aplicar Sent, Delivered, Read, Played ou Deleted. chat pode ser string |
messages_edit | Atualizar o conteúdo do ID original; não criar nova mensagem |
groups | Ler data com action, groupJid, assunto e alterações de participantes |
call | Ler data com callId, direction, status, endReason e endedBy; emitido por instâncias calls |
call_settings | Aplicar data.callsMaxConcurrent e data.incomingCallMode por data.callsConfigVersion à instância calls, sem criar um registro de chamada |
limits | Ler data e apresentar a restrição da sessão |
undecryptable | Registrar estado de mensagem indecifrável sem inventar seu conteúdo |
O formato detalhado e os exemplos estão em EVENTS.md. Reações chegam como messages com messageType: ReactionMessage. Mensagens enviadas pela API e edições próprias têm projeções filtradas; o Atendro deve registrar a resposta do envio/edição e reconciliar por ID. Não dependa de um eco do webhook.
Para a resposta automática de chamadas, processe somente a decisão confirmada data.autoReplyRequested=true, com direction=inbound, status=ended, endReason=rejected, endedBy=local, rejectionSource=policy e incomingCallMode=reject_reply. Registre a solicitação de forma durável junto do recebimento do evento, deduplique por instância/chamada e use a configuração imutável correspondente a callsConfigVersion. A identidade do chamador deve ficar restrita à empresa; um LID não deve ser convertido em número por remoção do sufixo. Envie pelo canal de mensagens explicitamente vinculado, com proteção contra repetição e resultado de entrega independente da recusa.
history, chats, qrcode, presence, contacts, labels, chat_labels, blocks, sender, newsletter_messages são aceitos por compatibilidade, mas aparecem em ignoredEvents. Histórico é consultado por /chat/find e /message/find. Estados de ligações são entregues por call nas instâncias de tipo calls.
Duplicação, ordem e recuperação
Instâncias calls entregam chamadas, configuração e ciclo de vida; eventos de mensagens, recibos, edições, grupos e undecryptable são filtrados, inclusive no backlog. Inscreva explicitamente call_settings além de call e connection. A atualização do limite não altera o cadastro do webhook. Para sincronizar, faça GET /instance/status inicialmente e compare instance.callsConfigVersion com a versão persistida no Atendro. connection pode acrescentar o limite e a versão em instance; call_settings usa data. Aplique ambos pela versão, nunca por horário de chegada; campos opcionais ausentes não apagam a configuração conhecida. Leia o prompt de implementação. As adições são anunciadas por capabilities.calls_settings_webhook e ainda dependem de publicação no servidor utilizado.
A entrega é pelo menos uma vez. seq é crescente por instância, com lacunas possíveis; não é um contador exclusivo daquela instância. Eventos filtrados e recibos condensados não geram POST. Estados de entrega de mensagens não devem regredir quando ocorrer replay. Uma inbox deve processar todos os itens de um recibo em lote antes de marcar o evento concluído.
2xx confirma a entrega. 400/410/413/422 encerram imediatamente as tentativas; 401/403 encerram após 3. Rede, timeout e demais códigos usam retry com backoff, até 20 tentativas ou 48 horas. Após 10 falhas transitórias consecutivas há uma pausa de 5 minutos. A requisição já em trânsito pode concluir após uma mudança de configuração; a próxima entrega usa o cadastro atualizado.
Consulte GET /webhook e /webhook/errors, ou as versões administrativas por ID. Com o token da instância, liste GET /v1/events?status=dead e use POST /v1/events/{id}/replay depois de corrigir o receptor. Se sua inbox já aceitou o evento, retente o processamento interno dela: reenviar o mesmo ID não deve duplicar a alteração. Trocar o destino não transfere eventos anteriores.
Homologação do novo receptor
Validar assinatura válida/inválida, janela de timestamp, instância de outro servidor/empresa, duplicatas concorrentes, falha de persistência, recibos com múltiplos IDs, mudança de conexão, reação, edição, mídia e recuperação após indisponibilidade. Não registrar payloads, números, conteúdo de conversas ou segredos em logs. Esta revisão testa o contrato e a entrega local com dados sintéticos; a homologação no Atendro real depende da implementação desse receptor.
Registrar atendimento de ligação
Assine call no webhook da instância de voz ou no webhook global herdado. O parceiro registra atendimento ao receber EventType=call com data.event=answered, data.answered=true e data.answeredAt. Nesse instante data.status=connecting; active indica mídia iniciada depois. Faça upsert por (instanceId, data.callId) e deduplique por eventId; o reenvio preserva esse ID. Os estados posteriores mantêm answeredAt.
Subeventos recording_ready/recording_failed apenas atualizam a gravação. O download exige header token e deve ser intermediado pelo backend do Atendro. Contrato completo de chamadas.
Com gravação de vídeo habilitada no provedor, video_recording_ready e video_recording_failed chegam no mesmo EventType=call, com callId e videoRecording: {status, contentType: "video/mp4", durationMs, bytes, failureReason}. Guarde esse objeto separadamente do recording do WAV. Não interprete esses subeventos como encerramento ou alteração de vagas: eles não substituem ended, endReason ou endedBy, e podem ser processados antes ou depois do evento terminal da chamada.
Deduplicate pelo eventId e use o seq do envelope para atualizações da gravação. Reconciliar por GET /call/{id} ou histórico; processing é estado consultável, sem webhook de progresso obrigatório. O download GET /call/{id}/recording/video exige proxy autenticado do backend, header token e repasse de Range, mantendo as credenciais fora do player. O guia de gravação de vídeo inclui o prompt de implementação para o atendro-ai e o tratamento de retenção e falhas.