Webhooks
Check the signature
The signature is the hex HMAC-SHA256 of the timestamp, a dot and the raw body, made with the webhook’s signing secret (whsec_…, shown once when the webhook is added). Refuse requests whose timestamp is more than 5 minutes old.
Lost the secret? New signing secret in the webhook’s window makes another one, shown once. The old one stops matching right away.
Node.js
import crypto from "node:crypto";
// rawBody: the request body exactly as received, before any JSON parsing.
export function fromSalesteam(rawBody, headers, secret) {
const timestamp = headers["x-salesteam-timestamp"] || "";
const expected = "v1=" + crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
const given = headers["x-salesteam-signature"] || "";
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
return fresh && given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}Python
import hashlib, hmac, time
# raw_body: the request body exactly as received (bytes), before any JSON parsing.
def from_salesteam(raw_body: bytes, headers, secret: str) -> bool:
timestamp = headers.get("X-Salesteam-Timestamp", "")
expected = "v1=" + hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
fresh = timestamp.isdigit() and abs(time.time() - int(timestamp)) < 300
return fresh and hmac.compare_digest(expected, headers.get("X-Salesteam-Signature", ""))