// HaulProof webhook receiver for Node 18+ (no packages): checks X-HaulProof-Signature over the raw body and refuses // timestamps older than 5 minutes (replays). Usage: HP_WEBHOOK_SECRET=whsec_... PORT=8080 node verify-webhook.mjs import { createHmac, timingSafeEqual } from "node:crypto"; import { createServer } from "node:http"; const SECRET = process.env.HP_WEBHOOK_SECRET; if (!SECRET) throw new Error("set HP_WEBHOOK_SECRET (returned once by POST /v1/webhooks)"); export function verify(rawBody, timestamp, signature, secret = SECRET, now = Date.now()) { if (!/^\d+$/.test(timestamp ?? "") || Math.abs(now / 1000 - Number(timestamp)) > 300) return false; const want = Buffer.from(`sha256=${createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex")}`); const got = Buffer.from(signature ?? ""); return got.length === want.length && timingSafeEqual(got, want); } createServer((req, res) => { let raw = ""; req.on("data", (d) => (raw += d)).on("end", () => { if (!verify(raw, req.headers["x-haulproof-timestamp"], req.headers["x-haulproof-signature"])) { res.writeHead(401).end("bad signature"); return; } const { event, test, data } = JSON.parse(raw); console.log(new Date().toISOString(), event, test ? "(test)" : "", data.type, data.severity, data.carrier?.key, data.carrier?.name); res.writeHead(204).end(); // answer fast; do slow work after replying }); }).listen(Number(process.env.PORT ?? 8080), () => console.log(`listening on :${process.env.PORT ?? 8080}`));