Webhooks de saída (ADR-026)
O SFA notifica sistemas externos via HTTP quando um evento interno ocorre — o oposto dos webhooks de entrada (que recebem de fora). O admin cria uma assinatura e escolhe o que dispara (o gatilho), opcionalmente filtrando por conector.
Como funciona
- Um evento de domínio é gravado no outbox (ex.: o executor de sync emite
SyncStarted/SyncCompleted/SyncFailedcomconnectorIdno payload). - O relay faz fan-out: além da fila do pipeline, enfileira o evento numa 2ª fila
webhook-deliveries. - O dispatcher casa as assinaturas (por gatilho + filtro de conector) e enfileira uma
entrega por (evento, assinatura) —
jobIddeterminístico garante idempotência. - O worker de entrega faz o
POSTassinado, com retry/backoff/DLQ, e grava cada tentativa (WebhookDispatch) para auditoria e reprocessamento.
Gatilhos disponíveis
| Gatilho | Evento | Filtro |
|---|---|---|
sync.started / sync.completed / sync.failed | execução de um conector | por conector (opcional) |
order.confirmed / order.rejected | pedido | — |
webhook.received | webhook de entrada recebido | — |
O catálogo é extensível: novos eventos de domínio viram gatilhos adicionando uma linha no
mapa events/webhook-queue.ts (backend) + no catálogo da UI.
Assinatura
Campos: name, url, eventType (key do gatilho), connectorId? (filtro), headers extras,
enabled, e um segredo HMAC. O segredo é cifrado em repouso (AES-256-GCM) e devolvido
uma única vez na criação/rotação — guarde-o para verificar a assinatura.
Verificando a entrega
Cada POST leva os headers:
X-SFA-Event: a key do gatilho (ex.:sync.completed).X-SFA-Delivery: id da entrega (eventId:subscriptionId).X-SFA-Signature:sha256=<hmac>— HMAC-SHA256 do corpo bruto com o segredo da assinatura.
O receptor recomputa o HMAC do corpo e compara (timing-safe). Corpo:
{ "eventId": "…", "eventType": "sync.completed", "occurredAt": "…", "orgId": "…", "data": { "connectorId": "…", "stats": { } } }
UI e API
Tela Webhooks (/dashboard/webhooks): aba Assinaturas (CRUD + testar + rotacionar segredo)
e Entregas (histórico + status + reprocessar). RBAC webhook:read / webhook:write.
Rotas sob /api/webhooks-out/*: GET/POST /subscriptions, GET/PATCH/DELETE /subscriptions/:id,
POST /subscriptions/:id/{rotate-secret,test}, GET /dispatches, POST /dispatches/:id/reprocess.
Esquemas completos na Referência da API (Scalar), tags Webhooks de saída · ….
Depende de Redis (como o pós-processamento). O worker é gated por WORKER_ENABLED; tunável via
WEBHOOK_DELIVERY_ATTEMPTS, WEBHOOK_DELIVERY_CONCURRENCY, WEBHOOK_TIMEOUT_MS.