Pular para o conteúdo principal

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​

  1. Um evento de domínio é gravado no outbox (ex.: o executor de sync emite SyncStarted/SyncCompleted/SyncFailed com connectorId no payload).
  2. O relay faz fan-out: além da fila do pipeline, enfileira o evento numa 2ª fila webhook-deliveries.
  3. O dispatcher casa as assinaturas (por gatilho + filtro de conector) e enfileira uma entrega por (evento, assinatura) — jobId determinístico garante idempotência.
  4. O worker de entrega faz o POST assinado, com retry/backoff/DLQ, e grava cada tentativa (WebhookDispatch) para auditoria e reprocessamento.

Gatilhos disponíveis​

GatilhoEventoFiltro
sync.started / sync.completed / sync.failedexecução de um conectorpor conector (opcional)
order.confirmed / order.rejectedpedido—
webhook.receivedwebhook 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 · ….

Infra

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.