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/
Have feedback?
Help us make this page even more useful.
Tell us what you'd like to see expanded, which examples would help, or what workflow you want covered next. Every message gets read.