Verifying the signature
Set a signing secret on a webhook output and every request carries:
| Header | Meaning |
|---|---|
X-Obserae-Signature | sha256=<hex>, an HMAC-SHA256 over "<timestamp>.<rawBody>". |
X-Obserae-Timestamp | Unix seconds, and part of the signed material — so a captured request cannot be replayed later. |
X-Obserae-Delivery | Unique per delivery. Deduplicate on it. |
X-Obserae-Event | alert.fired / alert.resolved / alert.acknowledged / test. Route without parsing the body. |
X-Obserae-Schema | obserae.alert/2. |
X-Obserae-Instance | Which instance sent it. |
User-Agent | obserae/<version>. |
Verify the raw body, before any JSON parsing. Re-serialising changes the bytes and the signature will not match.
Python
import hashlib, hmac, time
def verify(secret: str, headers, raw_body: bytes, max_age: int = 300) -> bool:
ts = headers.get("X-Obserae-Timestamp", "")
sig = headers.get("X-Obserae-Signature", "")
if not ts.isdigit() or abs(time.time() - int(ts)) > max_age:
return False # too old: a replayed capture
mac = hmac.new(secret.encode(), (ts + ".").encode() + raw_body, hashlib.sha256)
return hmac.compare_digest("sha256=" + mac.hexdigest(), sig)
Go
func verify(secret string, h http.Header, rawBody []byte, maxAge time.Duration) bool {
ts := h.Get("X-Obserae-Timestamp")
sent, err := strconv.ParseInt(ts, 10, 64)
if err != nil {
return false
}
if d := time.Since(time.Unix(sent, 0)); d > maxAge || d < -maxAge {
return false // too old (or too far in the future): a replayed capture
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(ts + "."))
mac.Write(rawBody)
want := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(want), []byte(h.Get("X-Obserae-Signature")))
}
Both use a constant-time comparison: a plain == leaks, one byte at a time, how
close a forged signature is.
These two snippets are not written from the specification — they are run, in
obserae’s own test suite, against a real signed v2 delivery produced by the
daemon (TestDocumentedVerifiersAcceptARealDelivery). If they ever stopped
accepting one, the build would fail.