Webhooks Design Decisions
Design record for inbound webhook support. Feature documentation lives in docs/features/webhooks.md.
Decisions Log
| # | Decision | Rationale |
|---|---|---|
| 1 | Webhook trigger semantics — webhook calls create_run_from_request() | Keeps webhook execution purely event-driven and flow-target based. |
| 2 | public policy for /hooks/{path_token} | HMAC verification is handled in the service layer, not by RBAC middleware. The endpoint must be accessible without JWT. |
| 3 | Reuse secrets table for HMAC keys | WebhookEndpoint.secret_id FK → secrets.id. Leverages existing secret-backend resolution. No new secret storage mechanism. |
| 4 | enabled: bool instead of status enum | Matches NotificationDestination pattern. Binary state (on/off) is sufficient — avoids enum sprawl. |
| 5 | Store body SHA-256 hash, never raw body | Compliance-safe. Hash in request_body_hash enables correlation without retention risk. |
| 6 | Phase 4 outbound webhooks = new NotificationDestination type | Reuses subscription model, Temporal dispatch workflow, and delivery tracking. No parallel system. |
| 7 | Phase 1 rate limiting: in-memory | Pragmatic for single-instance deployment. Migrate to Redis in Phase 2+ if horizontal scaling is needed. |
| 8 | Single-flow targeting (supersedes the earlier webhook_endpoint_targets N:N association) | Each endpoint targets exactly one flow via webhook_endpoints.flow_id. Nested child flows (flow.run steps) made webhook-level fanout redundant — a webhook that must start several flows targets one parent flow whose graph runs the children. Migration 039_webhook_single_flow aborts if a live endpoint still links multiple flows. |
| 9 | Fanout lives in the flow graph, not the webhook layer | Inbound execution creates exactly one run of the linked flow; WebhookTargetType survives only as response vocabulary (WebhookTriggeredRun.target_type). |