Refillo API

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")