Risk event webhooks

Receive versioned archdev.risk_event deliveries, verify the Stripe-style signature, and deduplicate on the webhook id.

Public contract, versioned. archdev.risk_event v1 is defined by services/go/archdev/configs/json_schemas/archdev-risk-webhook-v1.yaml. Breaking changes ship as a new version with a new schema file; v1 payloads keep validating. A signed example lives in services/go/archdev/fixtures/risk-webhook/.

Body

Each delivery is a JSON object with this shape:

Field Meaning
id Message id. Equals the ArchDev-Webhook-Id header; the idempotency key.
type Always archdev.risk_event.
version Always 1 for v1.
created_at RFC 3339 UTC creation time.
org.id Owning organization.
event pr.created, pr.updated, pr.merged, or pr.closed.
subject The pull request (type: pull_request, number, title, url).
repository full_name as owner/repo.
risk combined (low, medium, high, critical), uncertainty and consequence (low, medium, high).
summary Message content: human-readable risk summary.
room thread_id, message_id (equals the body id), and url.

Headers

Header Value
ArchDev-Webhook-Id The body id. Deduplicate on it.
ArchDev-Signature t=<unix seconds>,v1=<hex>[,v1=<hex>...], where each v1 entry is hex(hmac_sha256(secret, t + "." + raw_body)) over the exact raw request bytes. Multiple v1 entries are allowed during secret rotation; a signature is valid when any listed entry matches any accepted secret.

Verification

  1. Read the raw request body bytes. Never re-serialize parsed JSON: key order and whitespace change the HMAC.
  2. Split the header on ,. Require an integer t and at least one v1 hex entry.
  3. Reject when |now - t| exceeds 300 seconds (5-minute tolerance in both directions).
  4. Compute the expected HMAC per accepted secret and compare with a constant-time compare (crypto/subtle.ConstantTimeCompare in the Go reference at services/go/archdev/internal/riskwebhook/riskwebhook.go).
  5. Treat redelivery as normal: the dispatcher sends at least once with a frozen payload (same id, same bytes) until acknowledged, so store handled webhook ids and ignore repeats.

Reference

cd services/go/archdev && go test -count=1 ./internal/riskwebhook/