WEBHOOKS
Signature verification
Every delivery is signed so you can prove it came from HL Hunt and was not tampered with.
Headers
Content-Type: application/json HLHunt-Event-Id: evt_<id> HLHunt-Signature: t=<unix-seconds>,v1=<hex-hmac>[,v1=<hex-hmac>]
The signature is HMAC-SHA256(secret, "<t>.<raw body>"), hex-encoded. Verify against the raw body exactly as received — never re-serialize the JSON first. Reject timestamps outside a 5-minute replay window.
During a secret rotation the header carries two v1values — the current secret's first, the previous secret's second, for 24 hours. Accept the delivery if any v1 verifies with a secret you hold.
Verification example (Node.js)
const crypto = require('node:crypto')
function verify(rawBody, header, secret, maxSkewSeconds = 300) {
const parts = header.split(',')
const t = Number(parts.find((p) => p.startsWith('t=')).slice(2))
if (Math.abs(Date.now() / 1000 - t) > maxSkewSeconds) return false
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex')
return parts
.filter((p) => p.startsWith('v1='))
.some((p) => {
const candidate = Buffer.from(p.slice(3), 'hex')
const want = Buffer.from(expected, 'hex')
return candidate.length === want.length && crypto.timingSafeEqual(candidate, want)
})
}