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_eventv1 is defined byservices/go/archdev/configs/json_schemas/archdev-risk-webhook-v1.yaml. Breaking changes ship as a newversionwith a new schema file; v1 payloads keep validating. A signed example lives inservices/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
- Read the raw request body bytes. Never re-serialize parsed JSON: key order and whitespace change the HMAC.
- Split the header on
,. Require an integertand at least onev1hex entry. - Reject when
|now - t|exceeds 300 seconds (5-minute tolerance in both directions). - Compute the expected HMAC per accepted secret and compare with a constant-time compare (
crypto/subtle.ConstantTimeComparein the Go reference atservices/go/archdev/internal/riskwebhook/riskwebhook.go). - 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/
Need something clearer?
Tell us where this page still falls short.
If a step is confusing, a diagram is misleading, or a workflow needs a better example, send feedback directly and we will tighten it.