# Research: Webhook VM112 → Ops Platform **Date**: 2026-06-08 **Feature**: 001-webhook-vm112-integration ## R1 — Estado do receptor Ops (VM122) **Decisão**: Reutilizar endpoint MVP existente; estender com idempotência. **Evidência**: - `GET /health` → `{"status":"ok","service":"ligbox-ops-api","version":"0.1.0-mvp"}` - `POST /api/v1/webhooks/onboard` aceita `WebhookPayload` + header `X-Webhook-Secret` - Tabelas `webhook_events`, `tickets` já criadas em `init_db()` - Tenant VM112 pré-registado (id=1) **Alternativas rejeitadas**: - Novo microserviço webhook → YAGNI, endpoint já existe - Redis pub/sub directo → menos auditável que HTTP + SQLite ## R2 — Estado do emissor Portal (VM112) **Decisão**: Novo módulo `ops_webhook.py`; hook em `create_account`. **Evidência**: - Portal health: `http://10.10.10.112:8090/api/onboarding/health` OK - `create_account` retorna sem webhook actual - `session_id` via `X-Onboarding-Session` header (`deps.bind_onboarding_session`) - Notificações email existem (`notifications.py`) — webhook é canal adicional **Alternativas rejeitadas**: - Polling Ops → VM122 worker já faz poll health; não substitui eventos push - ntfy como substituto → fora de scope; ops desk precisa tickets ## R3 — Autenticação **Decisão**: Header `X-Webhook-Secret` partilhado; mesmo padrão do MVP. **Evidência**: Receptor já valida `x_webhook_secret != WEBHOOK_SECRET` → 401 **Produção**: Gerar secret forte (`openssl rand -hex 32`); configurar em ambos `.env` no mesmo deploy. ## R4 — Idempotência **Decisão**: Chave natural `(event_type, session_id, domain)` — consultar `webhook_events` antes de INSERT ticket. **Alternativas rejeitadas**: - UUID por evento no portal → mais complexo; session_id já existe - UNIQUE constraint DB → requer migration; lookup é suficiente para MVP volume ## R5 — Non-blocking **Decisão**: `BackgroundTasks` do FastAPI no portal para emitir webhook após response preparada, OU try/except inline com timeout curto (5s). **Preferência**: `BackgroundTasks` — zero impacto na latência percebida pelo cliente. ## R6 — Rede **Decisão**: URL fixa LAN `http://10.10.10.122:8080` — sem Traefik nesta fase. **Constitution**: LAN-only ✅; API bindada a `10.10.10.122` no docker-compose ✅ ## R7 — Código fonte | VM | Path deploy | Path fonte (versionar) | |----|-------------|------------------------| | 112 | `/opt/ibytera-mail-portal/` | `obsidian-infra/carbonio/ibytera-mail-portal/` | | 122 | `/opt/ligbox-ops-platform/` | `workspace/projects/ligbox-ops-platform/` |