Webhooks
Signed event deliveries to your server.
- Endpoints and eventsSigned POSTs to your server when a calendar figure is released or revised, or one of your own alerts fires.
- Verify signaturesCheck the ChartDeck-Signature header with Node, Python, the SDKs, or plain HMAC-SHA256 in any language.
- Signed alert webhooksThe per-alert webhooks of ChartDeck alerts are signed too - same signature header, same check, body unchanged.
#Verify
Every webhook ChartDeck sends is signed: API webhook endpoints and the webhook you can put on a ChartDeck alert. The header is
ChartDeck-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>The secret starts with whsec_cd_: for an API endpoint it is shown once when you add it; for alert webhooks it is the signing secret in your Alert settings (one per account). To verify a delivery:
- Take the raw body bytes as received, before parsing the JSON.
- Compute HMAC-SHA256 with the secret over
t + "." + raw body, hex in lower case. - Compare it with each
v1=value in constant time; accept when one matches. - Reject when
tis more than 300 seconds from your clock.
Python:
import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = [p.split("=", 1) for p in (header or "").split(",") if "=" in p]
t = next((v for k, v in parts if k.strip() == "t"), None)
sigs = [v.strip() for k, v in parts if k.strip() == "v1"]
if t is None or not sigs or abs(time.time() - int(t)) > tolerance:
return False
want = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(s, want) for s in sigs)JavaScript (Node):
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, header, secret, toleranceSec = 300) {
if (!header || !secret) return false;
let t = NaN; const sigs = [];
for (const part of header.split(',')) {
const [k, v] = part.split('=');
if (k?.trim() === 't') t = Number(v); else if (k?.trim() === 'v1') sigs.push(v.trim());
}
if (!Number.isFinite(t) || !sigs.length || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const want = Buffer.from(createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex'));
return sigs.some((s) => { const got = Buffer.from(s); return got.length === want.length && timingSafeEqual(got, want); });
}The SDKs have this built in (chartdeck.construct_event, constructEvent in @chartdeck/api). More detail: Verify signatures and Signed alert webhooks.