Pular para o conteúdo principal

Webhooks

Webhooks entregam eventos em tempo real do Canal de Denúncias e da Ouvidoria para um endpoint HTTPS do seu sistema, eliminando a necessidade de polling.

:::info Recurso do painel, não da API de integração Webhooks são configurados dentro do painel Nearone (Configurações do canal → Webhooks) por um administrador da organização, autenticado por sessão (JWT) — não por chave de API. Não existe um endpoint POST /webhooks na superfície /api/integrations/v1 descrita no restante deste site. Esta página documenta o payload e o contrato de entrega das notificações, que são o que importa para quem constrói o lado receptor da integração. :::

Como habilitar

  1. No painel, acesse Configurações do canal → Webhooks (dentro do Canal de Denúncias ou da Ouvidoria, conforme o módulo).
  2. Informe a URL HTTPS do seu endpoint e selecione os eventos de interesse.
  3. Ao salvar, a Nearone gera um segredo — exibido uma única vez — usado para assinar cada entrega.

Catálogo de eventos

Canal de Denúncias (complaint.*)

EventoQuando dispara
complaint.receivedNova denúncia registrada (pública ou por link de acesso)
complaint.status_changedStatus do caso muda (recebida → em análise → resolvida/arquivada, incl. reabertura)
complaint.assignee_changedResponsável pelo caso é atribuído ou removido
complaint.message_receivedDenunciante envia nova mensagem na thread anônima do protocolo
complaint.investigation_concludedConclusão da apuração é aprovada (fluxo de dupla checagem — 4-eyes)
complaint.legal_hold_setRetenção legal (legal hold) é ativada no caso
complaint.legal_hold_releasedRetenção legal é liberada
complaint.sla_breachedUm dos prazos de SLA do caso estoura (acolhimento, retorno ou resolução)

Ouvidoria (manifestation.*)

EventoQuando dispara
manifestation.receivedNova manifestação registrada
manifestation.status_changedStatus muda (recebida → acolhida → em tratamento → respondida/encerrada)
manifestation.assignee_changedResponsável é atribuído ou removido
manifestation.message_receivedManifestante envia nova mensagem na thread do protocolo
manifestation.formal_response_registeredResposta formal é registrada, encerrando o ciclo de tratamento
manifestation.reclassifiedManifestação é reclassificada para o Canal de Ética (denúncia grave)
manifestation.sla_breachedUm dos prazos de SLA estoura (acolhimento ou resposta)

Formato da entrega

Toda entrega é um POST HTTPS com o corpo:

{
"id": "b3bd5ccd-b182-4608-ab36-a404744ab135",
"type": "complaint.received",
"created_at": "2026-08-01T19:04:55.178994+00:00",
"data": { "...": "campos específicos do evento, ver abaixo" }
}
CampoDescrição
idIdentificador único do evento. Estável entre retentativas da mesma tentativa de entrega — use-o para processamento idempotente do seu lado, já que uma entrega pode ser reenviada.
typeUm dos eventos do catálogo acima.
created_atTimestamp ISO 8601 (UTC) de quando o evento ocorreu.
dataCorpo específico do evento — ver exemplos abaixo.

:::info data nunca contém identidade do denunciante/manifestante O payload traz apenas metadados do caso — código do protocolo, categoria, status, severidade/prioridade, timestamps. Ele nunca inclui dados pessoais de quem denunciou ou manifestou (nome, e-mail, conteúdo textual identificável). O anonimato/confidencialidade do Canal de Denúncias e da Ouvidoria é um invariante do produto — o payload do webhook respeita a mesma barreira de identidade que o restante da plataforma. :::

Exemplos de data por evento:

complaint.received
{
"protocol_code": "DEN-2026-00042",
"category": "assedio_moral",
"severity": "high",
"status": "under_review",
"submitted_at": "2026-08-01T12:00:00Z"
}
complaint.status_changed
{
"protocol_code": "DEN-2026-00042",
"category": "assedio_moral",
"severity": "high",
"status": "under_review",
"previous_status": "received",
"note": "Investigação iniciada."
}
manifestation.sla_breached
{
"protocol_code": "OUV-2026-00042",
"deadline_type": "response",
"due_at": "2026-08-01T12:00:00Z"
}

Cabeçalhos HTTP

Toda entrega inclui:

HeaderDescrição
Nearone-Event-IdMesmo valor do campo id do corpo.
Nearone-Event-TypeMesmo valor do campo type do corpo.
Nearone-TimestampUnix timestamp (segundos) do momento da assinatura.
Nearone-SignatureAssinatura HMAC-SHA256, ver abaixo.
User-AgentNearOne-Webhooks/1.0.
Content-Typeapplication/json.

Verificação de assinatura

A assinatura segue o esquema popularizado pela Stripe:

signature = HMAC-SHA256(secret, f"{timestamp}.{raw_request_body}")

Codificada em hexadecimal e comparada em tempo constante. timestamp é o valor exato do header Nearone-Timestamp; raw_request_body é o corpo bruto da requisição, byte a byte.

import hashlib
import hmac

def verify_signature(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
signed_payload = f"{timestamp}.{body.decode()}".encode()
expected = hmac.new(secret.encode(), signed_payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
const crypto = require("crypto");

function verifySignature(secret, timestamp, rawBody, signature) {
const signedPayload = `${timestamp}.${rawBody}`;
const expected = crypto.createHmac("sha256", secret).update(signedPayload).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

:::caution Use sempre o corpo bruto da requisição Assine/verifique contra os bytes brutos recebidos, nunca contra uma versão do JSON parseada e reserializada — reserializar pode produzir uma sequência de bytes diferente (ordem de chaves, espaçamento, escaping) e quebrar a verificação. Na maioria dos frameworks web isso significa ler o corpo antes de qualquer parsing automático (express.raw(), request.get_data() no Flask, etc.) e comparar apenas com hmac.compare_digest / crypto.timingSafeEqual — nunca ==/===, que são vulneráveis a ataques de tempo. :::

O segredo do webhook

O segredo é exibido uma única vez — no momento da criação do webhook, e novamente se você rotacionar. Se for perdido, a única recuperação é rotacionar para um novo segredo, o que invalida o anterior imediatamente. Recomendamos armazená-lo em um secrets manager, nunca em controle de versão.

Política de retentativas

Entregas com falha (resposta não-2xx ou timeout) são reenviadas automaticamente com backoff exponencial, num total de 6 tentativas:

TentativaAtraso desde a anterior
1ª retentativa30s
2ª retentativa2min
3ª retentativa10min
4ª retentativa30min
5ª retentativa1h
6ª retentativa (final)1h

Depois da 6ª tentativa sem sucesso, a entrega é marcada como failed (dead-lettered) permanentemente — ela não será reenviada automaticamente de novo. No log de entregas do painel, um humano pode disparar manualmente um "reenviar" pontual, que zera o contador de tentativas.

Circuit breaker

Se um endpoint acumular 20 entregas consecutivas totalmente esgotadas (dead-lettered), a Nearone pausa automaticamente o webhook (marca como inativo) em vez de continuar tentando entregar num endpoint claramente quebrado. As entregas pendentes são preservadas — não são perdidas — e voltam a ser processadas assim que o webhook é reativado manualmente no painel, que mostra um indicador de "desativado automaticamente" quando isso acontece.

Boas práticas

  • Responda 2xx rápido (na casa de poucos segundos) e faça o trabalho pesado de forma assíncrona depois — uma resposta lenta parece uma falha e dispara uma retentativa desnecessária, mesmo que sua aplicação fosse eventualmente ter sucesso.
  • Verifique a assinatura em toda requisição antes de confiar no payload — nunca processe um corpo de webhook não verificado.
  • Use Nearone-Event-Id para idempotência: a entrega é at-least-once, não exactly-once — o mesmo evento pode chegar mais de uma vez se uma retentativa e a tentativa original (atrasada) chegarem ambas ao seu endpoint.
  • Trate data como metadado, não como o registro completo do caso. Se sua integração precisa do detalhe completo, use o evento como gatilho para voltar ao painel Nearone e consultar a fonte oficial — hoje não existe um endpoint autenticado por chave de API para obter um caso completo por protocolo (só a UI do painel). Na prática, o uso viável agora é automação orientada a metadado ("postar um alerta no Slack quando severity=high", "abrir um chamado no seu ITSM"), não sincronização integral de dados.
  • Use apenas HTTPS. URLs http:// são rejeitadas no cadastro do webhook. Mantenha o certificado TLS do endpoint válido — certificados expirados aparecem como falhas de entrega.

Testando

O painel tem um botão "Testar" por webhook, que envia de forma síncrona um payload de amostra para qualquer um dos eventos assinados e mostra o resultado HTTP real inline (status, duração, corpo da resposta) — útil para validar seu código de verificação de assinatura contra uma requisição realmente assinada, sem esperar um evento real acontecer. Entregas de teste aparecem no log de entregas marcadas com is_test: true.

Ver também