Security
Validate signatures and operate a public HTTPS receiver safely.
Endpoint requirements
Webhook URLs must use public https:// endpoints on port 443. Credentials, fragments, redirects, loopback addresses, private networks, link-local addresses, multicast ranges, and non-public DNS answers are rejected. RoadOps resolves the hostname again for each connection and connects only to a validated public address.
Verify every signature
RoadOps signs the exact raw request body with HMAC-SHA256. Read RoadOps-Timestamp and RoadOps-Signature, reject timestamps outside your tolerance window, compute HMAC over <timestamp>.<raw-body>, and compare in constant time before parsing JSON. During the 24-hour rotation overlap, the signature header contains both valid v1 signatures.
import { createHmac, timingSafeEqual } from "node:crypto"
export function verifyRoadOps(rawBody: Buffer, timestamp: string, signatureHeader: string, secret: string) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false
const expected = createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest("hex")
return signatureHeader.split(",").filter(part => part.startsWith("v1=")).some(part => {
const received = Buffer.from(part.slice(3), "hex")
const wanted = Buffer.from(expected, "hex")
return received.length === wanted.length && timingSafeEqual(received, wanted)
})
}Store signing secrets in a secret manager. They are shown only when an endpoint is created or rotated and cannot be retrieved later.