Verify webhook signatures
Each request includes:
X-ErgOps-Webhook-Id: stable event UUID; deduplicate it.X-ErgOps-Webhook-Timestamp: Unix seconds.X-ErgOps-Webhook-Signature:v1=plus lowercase HMAC-SHA256 hex.
The signed bytes are ${timestamp}.${rawBody}. Verify against the raw body before JSON parsing using a constant-time comparison. Reject stale timestamps; five minutes is the recommended default tolerance unless your risk policy requires less.
const expected = crypto.createHmac("sha256", process.env.ERGOPS_WEBHOOK_SECRET)
.update(`${timestamp}.${rawBody}`).digest("hex");
const valid = crypto.timingSafeEqual(Buffer.from(signature.slice(3), "hex"), Buffer.from(expected, "hex"));
Webhook signing secrets must be recoverable by the delivery runtime, so ErgOps encrypts them at rest with a deployment-held key. API authentication secrets are different: only their irreversible hash is retained.