# Webhooks Receive signed events at your own URL. There are 64 event types, and globs such as `sandbox.*` work. ```sh HOOK=$(harebox webhook new --url http://127.0.0.1:9/hook --events '["sandbox.*","prompt.finished"]' | jq -r .id) harebox webhook test "$HOOK" harebox webhook deliveries "$HOOK" ``` ## Verify a signature `HareBox-Signature` is `t=TIMESTAMP,v1=HEX`, where the value is HMAC-SHA256 over `TIMESTAMP.DELIVERY_ID.RAW_BODY`. `DELIVERY_ID` is the `HareBox-Delivery` header. The body is the raw bytes as received, before any JSON parsing. Refuse a timestamp more than five minutes from your clock, so a copied delivery can't be replayed later. ```python import hashlib, hmac, time def verify(secret, header, delivery_id, body, tolerance=300, now=None): # During a secret rotation two signatures arrive: as two headers, or joined into one with commas. pairs = [p.strip().split("=", 1) for p in header.split(",") if "=" in p] stamps = {v for k, v in pairs if k == "t"} if len(stamps) != 1 or not min(stamps).isdigit(): return False t = stamps.pop() if abs((time.time() if now is None else now) - int(t)) > tolerance: return False # too old, or from the future: a replay want = hmac.new(secret.encode(), f"{t}.{delivery_id}.".encode() + body, hashlib.sha256).hexdigest() return any(hmac.compare_digest(want, v) for k, v in pairs if k == "v1") ``` If your framework gives repeated headers as a list, join them with commas before calling `verify`. Delivery is at least once, so use the event `id` to deduplicate. Retries run for about 72 hours with growing gaps, and `prompt.delta` goes to streams only. During a secret rotation, both signatures are sent until the overlap ends, so a receiver on either secret accepts the delivery.