▸ API · Webhooks

Eventos

scan.completed, scan.failed e finding.opened — quando saem e o que trazem.

atualizado em 2 set 2026versão v1

Três eventos, nomeados no padrão recurso.aconteceu. Todos chegam no mesmo envelope; o que muda é o data.

EventoQuando sai
scan.completedUma ronda terminoudetalhes ↓
scan.failedUma ronda falhoudetalhes ↓
finding.openedO plantão encontrou brecha novadetalhes ↓

scan.completedUma ronda terminou

Toda ronda que passa pela fila e é gravada como done — disparada no painel, por POST /scans ou pelas integrações.

As rondas que rodam por dentro, sem passar pela fila, NÃO emitem este evento: o plantão agendado (que tem o finding.opened) e o scan-on-deploy.
O relatório completo não vem no webhook. Use scan_id em GET /scans/{id}/report — assim a carga fica pequena e o conteúdo sensível sai por uma porta autenticada por você.

Campos de data

CampoDescrição
scan_id
stringsempre
Id da ronda.
hostname
stringsempre
Alvo normalizado.
target
stringsempre
URL escaneada.
risk_score
integersempre
Exposição (maior = pior).
risk_label
stringsempre
Faixa do score.
SECURELOW_RISKMODERATE_RISKHIGH_RISKCRITICAL_RISK
critical_count
integersempre
Achados critical.
blocked
booleansempre
A ronda não viu o app. Trate como inconclusivo, não como "seguro".
report_url
string
Link do relatório no painel.

Exemplo

POST · application/json
{
  "id": "evt_9f1c2a54-4c1e-4a0c-9a1f-7b6a2f0d8e33",
  "event": "scan.completed",
  "createdAt": "2026-08-29T14:22:07.481Z",
  "data": {
    "scan_id": "scan_5e2a91c4",
    "hostname": "app.exemplo.com.br",
    "target": "https://app.exemplo.com.br",
    "risk_score": 41,
    "risk_label": "MODERATE_RISK",
    "critical_count": 1,
    "blocked": false,
    "report_url": "https://www.guarita.dev/scans/scan_5e2a91c4"
  }
}

scan.failedUma ronda falhou

A ronda estourou o tempo máximo sem produzir resultado, ou a fila desistiu depois de todas as tentativas.

Assine junto com scan.completed: só um dos dois chega pra cada ronda. Tratar só o sucesso faz "ronda que não rodou" virar silêncio — indistinguível de "não encontrei nada".

Campos de data

CampoDescrição
scan_id
stringsempre
Id da ronda.
hostname
stringsempre
Alvo normalizado.
target
stringsempre
URL que seria escaneada.
reason
stringsempre
A causa, em português.

Exemplo

POST · application/json
{
  "id": "evt_0b7e4d21-8a3f-4c6e-b2d1-5f9a0c3e7d18",
  "event": "scan.failed",
  "createdAt": "2026-08-29T15:01:33.902Z",
  "data": {
    "scan_id": "scan_c3d9e0a1",
    "hostname": "app.exemplo.com.br",
    "target": "https://app.exemplo.com.br",
    "reason": "A ronda excedeu o tempo máximo e não produziu resultado."
  }
}

finding.openedO plantão encontrou brecha nova

Uma ronda de plantão (agendada) abriu ao menos um achado que não existia na ronda anterior do mesmo app. Ronda limpa não gera evento.

Não sai de ronda inconclusiva (bloqueada ou parcial por tempo): comparar um retrato incompleto com um completo inventaria "achados novos". Por isso blocked e incomplete vêm sempre false.
Na PRIMEIRA ronda de um app não há baseline: todos os achados contam como novos e verdict vem clean (a ronda estabelece a base). Se o seu tratamento abre chamado por item, considere ignorar eventos com previous_risk_score: null.
É independente do limiar de alerta por e-mail: o limiar é preferência de quem lê a caixa de entrada, não contrato de integração.

Campos de data

CampoDescrição
scan_id
stringsempre
Id da ronda de plantão.
hostname
stringsempre
O app.
verdict
stringsempre
breach = achado novo grave, ou salto de 15+ pontos · warning = piorou, sem gravidade · clean = primeira ronda (base).
breachwarningclean
risk_score
integersempre
Exposição agora.
previous_risk_score
integer | nullsempre
Exposição da ronda anterior. null quando não havia.
opened
string[]sempre
TÍTULOS dos achados novos (não ids — o título é a chave de identidade entre rondas).
resolved
string[]sempre
Títulos dos achados que sumiram desde a anterior.
blocked
booleansempre
Sempre false (ronda bloqueada não emite).
incomplete
booleansempre
Sempre false (ronda parcial não emite).

Exemplo

POST · application/json
{
  "id": "evt_2c7d0b18-6f4a-4b7e-9c31-0a5d8e12b774",
  "event": "finding.opened",
  "createdAt": "2026-08-29T03:11:52.094Z",
  "data": {
    "scan_id": "scan_a17b3f90",
    "hostname": "app.exemplo.com.br",
    "verdict": "breach",
    "risk_score": 58,
    "previous_risk_score": 41,
    "opened": [
      "Chave service_role do Supabase exposta no bundle",
      "Content-Security-Policy ausente"
    ],
    "resolved": [
      "Strict-Transport-Security (HSTS) ausente"
    ],
    "blocked": false,
    "incomplete": false
  }
}
Achou algo errado ou faltando? help@guarita.dev — com o x-request-id, se for sobre uma resposta.