Webhooks
Works locallyWorks in the local build today.
Receive signed events at your own URL. There are 64 event types, and globs such as sandbox.* work.
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"
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.
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.

