Webhooks
Refillo posts signed JSON to an https address you choose. Failures are retried for 24 hours.
The signature
The header is Refillo-Signature: t=<unix seconds>,v1=<hex hmac sha256 of the secret, over t + "." + raw body>. Reject a timestamp more than five minutes from your clock. This is the Node program the test runs.
import { createHmac, timingSafeEqual } from "node:crypto";
/** The check the webhooks guide shows. Same rule as Refillo's own verifier. */
export function verifyWebhook(secret: string, header: string, rawBody: string, nowSeconds: number): boolean {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(nowSeconds - t) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
const given = Buffer.from(parts.v1 ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}
if (process.argv[1]?.endsWith("verify-webhook.ts")) {
const secret = "whsec_example";
const body = '{"id":"1"}';
const t = 1_700_000_000;
const mac = createHmac("sha256", secret).update(`${t}.${body}`).digest("hex");
if (!verifyWebhook(secret, `t=${t},v1=${mac}`, body, t)) throw new Error("expected a valid signature");
if (verifyWebhook(secret, `t=${t},v1=${mac}`, "other", t)) throw new Error("expected a mismatch");
console.log("verify_webhook ok");
}
Python
The same check, with hmac.compare_digest. This is the Python program the test runs.
"""The webhook check from the docs. Run: python verify_webhook.py"""
import hashlib
import hmac
import time
def verify(secret: str, header: str, raw: bytes, now: int | None = None) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts["t"])
if abs((now or int(time.time())) - t) > 300:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
if __name__ == "__main__":
secret = "whsec_example"
body = b'{"id":"1"}'
t = 1_700_000_000
mac = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
header = f"t={t},v1={mac}"
assert verify(secret, header, body, t)
assert not verify(secret, header, b"other", t)
print("verify_webhook ok")