Webhooks
O Cotejar avisa o seu sistema quando um catálogo é aplicado, uma análise termina ou as divergências mudam. Cada entrega é assinada com HMAC-SHA256 e fica registrada com todas as tentativas.
Um destino pertence à empresa e exige webhook:gerenciar. Configurar, testar e reenviar exigem plano pago ativo; sem ele, a API responde 409 ASSINATURA_INATIVA.
Configurar o destino
Seção intitulada “Configurar o destino”Pelo painel, use Integrações › Webhooks. Pela API:
Crie com If-None-Match: * e Idempotency-Key. Para alterar, rotacionar ou reativar, consulte GET /v1/webhook e envie o ETag completo em If-Match.
{ "url": "https://integracao.example/cotejar/eventos" }A resposta traz o segredo (ctj_whsec_...), exibido nesta operação e na sua repetição idempotente. Guarde-o no gerenciador de segredos: as consultas seguintes não o mostram.
O destino precisa ser HTTPS público na porta 443, sem credenciais nem fragmento. Cada alteração gera um segredo novo e invalida novas entregas da versão anterior. A configuração não testa a conexão: use o evento de teste.
Revoga o destino com If-Match e Idempotency-Key. Uma requisição já em trânsito ainda pode chegar; novas entregas daquela versão deixam de ser autorizadas.
O que chega no receptor
Seção intitulada “O que chega no receptor”POST /cotejar/eventos HTTP/1.1Content-Type: application/json; charset=utf-8User-Agent: Cotejar-Webhooks/1Cotejar-Event-Id: 10cad8ab-68b8-48a6-a296-868597fb7ccbCotejar-Timestamp: 1792000000Cotejar-Signature: v1=<hmac-sha256-hexadecimal>
{"id":"10cad8ab-68b8-48a6-a296-868597fb7ccb","versao":1,"empresa":"12345678","ocorridoEm":"2026-10-13T16:48:03.875Z","dados":{"tipo":"catalogo.aplicado","catalogoId":"7c7b354f-07b6-4461-9240-263418c81cdb","versao":2}}| Cabeçalho | Valor |
|---|---|
Cotejar-Event-Id |
UUID estável do evento, para deduplicação. Igual a id no corpo. |
Cotejar-Timestamp |
Segundos Unix desta tentativa. |
Cotejar-Signature |
v1= seguido do HMAC-SHA256 hexadecimal. |
O envelope tem sempre id, versao (hoje 1), empresa (CNPJ raiz), ocorridoEm e dados. O tipo do evento está em dados.tipo.
Conferir a assinatura
Seção intitulada “Conferir a assinatura”A entrada do HMAC é timestamp + "." + event_id + "." + bytes_exatos_do_corpo, com o segredo completo, inclusive o prefixo ctj_whsec_. Confira a assinatura antes de interpretar o JSON, recuse instantes com diferença maior que cinco minutos e compare em tempo constante.
Depois da conferência, grave o ID do evento na mesma transação que aceita o processamento. Outra tentativa pode trazer novo instante e nova assinatura para o mesmo evento: não aplique o efeito de novo.
Eventos
Seção intitulada “Eventos”dados.tipo |
Quando | Campos em dados |
|---|---|---|
catalogo.aplicado |
Uma ingestão terminou em aplicada. |
catalogoId, versao (nova versão do catálogo) |
catalogo.revisao_necessaria |
Uma validação terminou em precisa_revisao. |
ingestaoId, catalogoId (pode ser null) |
analise.concluida |
Uma análise terminou em concluida. |
analiseId, regulamentoDegradado |
analise.falhou |
Uma análise terminou em falhou. |
analiseId |
divergencias.alteradas |
A vigilância encontrou mudança nos achados. | vigilanciaId, novas, reabertas, resolvidas |
vigilancia.degradada |
A vigilância deixou de estar em dia. | ultimaVarreduraEm, ultimaColetaUtilEm, varreduraVencida, regulamentoDegradado |
vigilancia.restabelecida |
A vigilância voltou a estar em dia. | Os mesmos de vigilancia.degradada |
webhook.teste |
Teste pedido no painel ou pela API. | destinoId |
{ "id": "73bbd6da-e275-4051-8c16-9bc7b861c5ce", "versao": 1, "empresa": "12345678", "ocorridoEm": "2026-10-13T16:47:03.109Z", "dados": { "tipo": "analise.concluida", "analiseId": "e0da8bf7-0252-4556-9d52-856cc1349507", "regulamentoDegradado": false } }O evento é um aviso, não o dado completo. Ao receber analise.concluida, leia o resultado em GET /v1/analises/{analiseId}. Não há garantia de ordem de entrega. Somente uma resposta HTTP 2xx concluída confirma a tentativa. Redirecionamentos não são seguidos.
Teste e reenvio
Seção intitulada “Teste e reenvio”| Rota | Uso |
|---|---|
GET /v1/webhook/eventos |
Eventos mais recentes e estado das entregas. |
GET /v1/webhook/eventos/{id} |
Envelope original e metadados sanitizados. |
GET /v1/webhook/eventos/{id}/tentativas |
Tentativas em ordem crescente. |
POST /v1/webhook/testes |
Agenda um evento webhook.teste. |
POST /v1/webhook/eventos/{id}/reenvios |
Abre novo ciclo para evento encerrado. |
O estado de cada evento é pendente, executando, entregue, falhou ou cancelado. Cada tentativa registra status_http e codigo_falha quando houver.
Teste e reenvio exigem destino ativo, plano pago ativo, ETag atual e idempotência. O 202 confirma o aceite na fila, não a conexão. Os dois POSTs compartilham o teto de cinco pedidos por minuto por empresa. O passo a passo do teste está em Testar com segurança.
O reenvio aceita evento entregue, falhou ou cancelado com menos de 30 dias. Ele preserva ID e corpo, abre outro ciclo de até oito tentativas em 24 horas e autoriza explicitamente o destino atual, inclusive depois de uma rotação. O receptor continua deduplicando pelo ID.
Retenção
Seção intitulada “Retenção”Eventos encerrados há mais de 30 dias ficam elegíveis para limpeza. Eventos pendentes ou em execução são preservados. Trinta dias não prometem exclusão instantânea se houver fila acumulada ou processamento indisponível.
Depois da retirada, a consulta devolve 404. Um recibo idempotente preservado ainda pode referenciar esse ID expirado; repetir o pedido não recria o evento nem outra entrega.

