01Por que este contrato existe
Um canal do tipo webhook manda, em tempo real, o mesmo ciclo de vida de alerta que o e-mail e o SMS já notificam: abertura, escalonamento, repetição sem reconhecimento e resolução, além do envio de teste do canal e do resumo diário (quando ligado). É a forma de integrar o evobits com um sistema próprio (um bot de chat, um sistema de chamados, um painel interno) sem depender de e-mail.
Nenhum endereço, credencial ou dado de configuração de dispositivo sai neste webhook: só o que já aparece no painel administrativo (mensagem do alerta, nomes de dispositivo e sensor, severidade, estado e os mesmos links que o e-mail usa).
02Como configurar
O canal é criado no painel administrativo (Alertas → Canais → tipo webhook), com a URL de destino. O segredo HMAC é gerado pelo evobits (nunca por quem cadastra o canal) e mostrado uma única vez, na criação ou na rotação do canal, com um aviso de que não será mostrado de novo. Guarde-o como guardaria qualquer segredo, nunca em texto plano versionado.
Por padrão só https:// é aceito, e endereços privados, de loopback ou de link-local são recusados na criação do canal e a cada entrega. Isso não é configurável pelo painel: é uma proteção do evobits contra o canal apontar sem querer para a própria rede interna de quem o configurou.
03Eventos e corpo
Content-Type application/json. O campo evento diz o que aconteceu:
| Evento | Quando |
|---|---|
| alerta.aberto | Um alerta novo foi aberto e o primeiro passo da regra foi notificado |
| alerta.escalonado | O alerta continua sem reconhecimento e passou para o próximo passo da regra |
| alerta.repeticao | O alerta continua aberto e sem reconhecimento; o último passo se repete |
| alerta.resolvido | A condição deixou de valer (só quando a regra notifica resolução) |
| teste | Envio de teste do canal, pelo botão Testar do painel |
| digest | Resumo diário do canal (só quando "Resumo diário" está ligado no canal) |
Corpo de um alerta.aberto:
{
"evento": "alerta.aberto",
"entrega": "8421",
"enviadoEm": "2026-09-24T14:32:10.000Z",
"tenant": { "slug": "acme" },
"alerta": {
"id": "512",
"regra": "CPU alta",
"severidade": "ruim",
"estado": "aberto",
"mensagem": "CPU em 95% (limiar 90%) em srv-01",
"abertoEm": "2026-09-24T14:29:00.000Z",
"fechadoEm": null,
"dispositivo": { "id": "…", "nome": "srv-01" },
"sensor": { "id": "…", "nome": "CPU", "tipo": "snmp_cpu" },
"detalhe": { "valor": 95, "unidade": "%", "limiar": 90 }
},
"links": {
"painel": "https://app.evobits.com.br/admin/alertas/512",
"reconhecer": "https://app.evobits.com.br/admin/alertas/ack/…"
}
}
dispositivo e sensor vêm null quando o alvo do alerta é uma sonda (condição sonda_offline): nesse caso a própria mensagem já descreve a sonda por extenso. links.reconhecer só existe em alerta.aberto, alerta.escalonado e alerta.repeticao: um alerta resolvido, um teste ou um digest não têm o que reconhecer.
Em teste não há campo alerta nenhum. Em digest o lugar de alerta é um resumo, com o período e três listas no mesmo formato do objeto alerta acima:
{
"evento": "digest",
"entrega": "9010",
"enviadoEm": "2026-09-24T11:00:00.000Z",
"tenant": { "slug": "acme" },
"resumo": {
"desde": "2026-09-23T11:00:00.000Z",
"ate": "2026-09-24T11:00:00.000Z",
"abertos": [ "…" ],
"fechados": [ "…" ],
"aindaAbertos": [ "…" ]
},
"links": { "painel": "https://app.evobits.com.br/admin/alertas" }
}
04Cabeçalhos
| Cabeçalho | O que é |
|---|---|
| Content-Type | application/json |
| User-Agent | evobits-webhook/1 |
| X-Evobits-Evento | O mesmo valor do campo evento do corpo |
| X-Evobits-Entrega | Id desta entrega, igual em toda reentrega da MESMA notificação (chave de idempotência) |
| X-Evobits-Timestamp | Instante do envio, em segundos desde a época Unix |
| X-Evobits-Assinatura | sha256=<hex>, ver a seção seguinte |
05Verificar a assinatura
A assinatura é o HMAC-SHA256, em hexadecimal, do texto <timestamp>.<corpo bruto>, com o segredo do canal como chave. Some sempre sobre o corpo bruto (os bytes recebidos, antes de qualquer JSON.parse): reformatar o JSON antes de assinar dá um resultado diferente do que o evobits calculou.
Janela de 5 minutos. Compare o X-Evobits-Timestamp com o relógio de vocês e recuse (sem processar) qualquer entrega fora de uma janela de 5 minutos para trás ou para a frente. Isso limita o estrago de uma entrega capturada e reenviada por um terceiro.
Comparação em tempo constante. Nunca compare a assinatura com === ou ==: use uma função de comparação que leve o mesmo tempo independente de onde a primeira diferença aparece (exemplos abaixo). Uma comparação comum vaza, pelo tempo de resposta, quantos caracteres iniciais acertaram.
Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verificar(corpoBruto, timestamp, assinaturaRecebida, segredo) {
const idade = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(idade) || idade > 300) return false; // janela de 5 min
const esperada = 'sha256=' + createHmac('sha256', segredo)
.update(`${timestamp}.${corpoBruto}`)
.digest('hex');
const a = Buffer.from(esperada);
const b = Buffer.from(assinaturaRecebida || '');
return a.length === b.length && timingSafeEqual(a, b);
}
Python:
import hmac
import hashlib
import time
def verificar(corpo_bruto: bytes, timestamp: str, assinatura_recebida: str, segredo: str) -> bool:
idade = abs(time.time() - float(timestamp))
if idade > 300: # janela de 5 min
return False
mensagem = timestamp.encode() + b'.' + corpo_bruto
esperada = 'sha256=' + hmac.new(segredo.encode(), mensagem, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperada, assinatura_recebida or '')
06Reentrega e idempotência
Uma resposta 2xx confirma a entrega. Qualquer outra coisa (4xx, 5xx, timeout de 10 segundos, erro de conexão) conta como falha e entra na fila de reentrega, com espera progressiva entre as tentativas: 1, 2, 5, 15 e 60 minutos. Sem uma resposta 2xx até a sexta tentativa, o evobits desiste e a entrega fica marcada como falha (visível no painel, na tela do alerta).
Um redirecionamento não é seguido: aponte a URL do canal direto para o endpoint final, sem passar por um encurtador ou por um redirect de HTTP para HTTPS.
Idempotente por X-Evobits-Entrega. Toda reentrega da MESMA notificação chega com o MESMO valor neste cabeçalho. Guarde os últimos ids processados (mesmo que só por algumas horas) e ignore uma entrega repetida em vez de agir duas vezes sobre o mesmo alerta.
07Como saber que está pronto
- 01O endpoint responde 2xx rapidamente, mesmo antes de terminar de processar o corpo.
- 02A assinatura é conferida sobre o corpo BRUTO, em tempo constante, e uma assinatura errada é rejeitada.
- 03Um X-Evobits-Timestamp fora da janela de 5 minutos é rejeitado.
- 04Os seis eventos (alerta.aberto, alerta.escalonado, alerta.repeticao, alerta.resolvido, teste, digest) são tratados sem quebrar em campos ausentes (dispositivo/sensor nulos, sem alerta em teste).
- 05Uma entrega repetida com o mesmo X-Evobits-Entrega não duplica a ação do lado de vocês.
- 06Um teste pelo botão Testar do painel chega e é reconhecido como evento: "teste".