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 é:

json
{
  "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çãoResultado
Salvar o globalAplica às instâncias atuais sem exceção e às novas, em transação
Salvar enabled:falsePausa as instâncias que herdam; a mesma URL mantém o cursor
Salvar a mesma URL sem secretPreserva o segredo anterior
Trocar URLComeç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ânciaCria 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.

HeaderConteúdo
X-AtendroZAP-Event-IdUUID do evento; igual a eventId
X-AtendroZAP-Instance-IdUUID da instância; igual a instanceId
X-AtendroZAP-Event-TypeTipo projetado; igual a EventType
X-AtendroZAP-Event-SeqSequência numérica; igual a seq
X-AtendroZAP-AttemptTentativa; informativo, não faz parte da assinatura
X-AtendroZAP-TimestampUnix em segundos, gerado a cada tentativa
X-AtendroZAP-Signaturesha256= 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:

  1. Resolver o servidor cadastrado a partir da rota e carregar seu segredo.
  2. Chamar verifyAtendroZAP(request, secret) com o corpo ainda intacto.
  3. Verificar o vínculo servidor → instância → empresa.
  4. 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.
  5. Responder 204/200 apenas depois do commit. Em falha temporária do banco, responder 503 para que o AtendroZAP tente novamente.
  6. 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.

TipoTratamento no Atendro
connectionLer state/status/connection e atualizar conexão. instance é objeto. QR não é enviado: consultar status autenticado
messagesUpsert de mensagem pelo ID externo; distinguir texto, mídia, contato, localização e ReactionMessage
messages_updateProcessar todos os event.MessageIDs; aplicar Sent, Delivered, Read, Played ou Deleted. chat pode ser string
messages_editAtualizar o conteúdo do ID original; não criar nova mensagem
groupsLer data com action, groupJid, assunto e alterações de participantes
callLer data com callId, direction, status, endReason e endedBy; emitido por instâncias calls
limitsLer data e apresentar a restrição da sessão
undecryptableRegistrar 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.

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

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.