Verifying signatures
Check every delivery's HMAC-SHA256 signature and timestamp before trusting it.
The signature#
X-Worxley-Signature is t=<unix seconds>,v1=<hex>, where v1 is an HMAC-SHA256 of t + "." + raw body keyed with your endpoint’s secret.
- Use the raw request body, before any JSON parsing.
- Reject timestamps more than 5 minutes old so a captured request can’t be replayed.
Example#
const crypto = require("crypto");
function verifyWorxley(rawBody, header, secret, toleranceSec = 300) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "");
if (!m) return false;
const [, t, v1] = m;
if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
// Express: keep the raw body for this route
app.post("/worxley-webhook", express.raw({ type: "application/json" }), (req, res) => {
if (!verifyWorxley(req.body.toString("utf8"), req.get("X-Worxley-Signature"), process.env.WORXLEY_WEBHOOK_SECRET)) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
queue.add(event); // handle later, dedupe on event.id
res.sendStatus(204);
});import hmac, hashlib, re, time
def verify_worxley(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", header or "")
if not m:
return False
t, v1 = m.groups()
if abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
# Flask
@app.post("/worxley-webhook")
def worxley_webhook():
if not verify_worxley(request.get_data(), request.headers.get("X-Worxley-Signature"), os.environ["WORXLEY_WEBHOOK_SECRET"]):
abort(400)
enqueue(request.get_json()) # dedupe on event["id"]
return "", 204Rotating a secret#
Rotate a secret from the endpoint’s menu. The new secret applies from the next delivery, so update your server straight after.