Skip to main content

Webhook Dispatcher — Security Model

Status: populated Owner: Platform Engineering / Security Last updated: 2026-04-18 Companion: API_CONTRACTS · DEPLOYMENT_TOPOLOGY

1. Threat Surface

SurfaceExposure
REST API /v1/webhooksExternal (via Kong); JWT-authenticated
Outbound HTTP to customer URLsExternal egress; adversarial responses possible
NATS consumerInternal cluster only
/health, /ready, /metricsInternal cluster only

2. Authentication & Authorisation

REST API (Kong)

  • JWT Bearer token validated by Kong before request reaches service.
  • accountId injected as X-Account-Id header by Kong after JWT validation.
  • Service trusts X-Account-Id only from Kong (NetworkPolicy restricts direct access).
  • All webhook CRUD operations verified against accountId — no cross-account access.

NATS

  • mTLS leaf node credentials; user webhook-dispatcher.
  • Scoped permissions: subscribe webhook.dispatch, publish webhook.dispatch.deadletter.

PostgreSQL

  • Dedicated service account hook_svc.
  • Grants: full CRUD on hook.webhook_configs, hook.delivery_attempts.
  • No access to dlr.*, orch.*, or other schemas.
  • TLS enforced.

3. Webhook Secret Security

At Rest

  • secret_enc column stores AES-256-GCM ciphertext.
  • Encryption key: KMS-managed CMK; application derives data key per-tenant using envelope encryption.
  • Plaintext secret never written to disk or logs.

In Transit (HMAC Signing)

  • Plaintext secret loaded into memory only during signing.
  • Memory not shared across concurrent goroutines.
  • Secret not returned in any API response after initial registration.
  • Customers may rotate by issuing PUT /v1/webhooks/:id with new secret.

Secret Rotation Impact

  • After secret rotation, any in-flight deliveries using the old secret will be rejected by the customer endpoint.
  • New delivery_attempts rows (including retries scheduled after the rotation) use the new secret automatically.

4. Outbound HTTP Security

ControlImplementation
TLS verificationSystem CA bundle; custom CA not supported
Redirect preventionredirect: 'manual'; 3xx treated as failure
Timeout5 s hard timeout via AbortSignal.timeout
SSRF preventionBlocked at NetworkPolicy level; internal cluster IPs not routable via egress
Response bodyOnly first 512 chars stored; never executed or parsed as code

SSRF Mitigation

DNS resolution occurs at delivery time. NetworkPolicy egress rules block access to:

  • 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (private ranges)
  • 169.254.169.254/32 (cloud metadata endpoint) Customer URLs pointing to these ranges will fail at the network layer, not application layer.

5. Rate Limiting

SurfaceLimit
REST API per account60 requests/minute (Kong rate-limit plugin)
Max webhooks per account10 (database-enforced trigger)
Outbound delivery concurrency20 per pod (NATS max_ack_pending)

6. Data Handling

DataHandling
Phone numbers (to field)Stored only in delivery_attempts.payload_snapshot (JSONB); not logged
Webhook secretsAES-256-GCM encrypted at rest; not in logs; not in API responses
response_body_previewFirst 512 chars of customer endpoint response; may contain arbitrary data; stored as text, never interpreted
accountIdInternal UUID; safe to log

7. Dependency Security

  • npm audit in CI; CRITICAL/HIGH block merge.
  • Base image: node:20-alpine.
  • Trivy scan in CI.
  • Dependabot for automated patch PRs.