API REFERENCE · WEBHOOKS
Webhooks
Hoonify can POST to your URL whenever something interesting happens — instance lifecycle changes, billing alerts, key rotations, pool incidents. Configure endpoints under Settings · Webhooks.
Event delivery
Each event is a single POST request with a JSON body and a Hoonify-Signature header. Hoonify retries non-2xx responses on an exponential schedule for ~24 hours: 0s, 30s, 2m, 10m, 1h, 3h, 6h, 12h, 24h. After the final attempt the event is recorded as delivery_failed and shown in the webhook log.
Headers
| Header | Value |
|---|---|
| Hoonify-Event | The event type (also in the body). Useful for routing without parsing. |
| Hoonify-Event-Id | Stable event ID. Use for dedup if your handler is not naturally idempotent. |
| Hoonify-Signature | t=<unix>,v1=<hmac-sha256>. See verification below. |
| Hoonify-Delivery | Per-attempt UUID. Increments per retry of the same event. |
Example payload
{
"id": "evt_3xKpWQ",
"object": "event",
"type": "instance.lifecycle",
"created": 1745784012,
"data": {
"instance": {
"id": "inst_2tFG9L",
"status": "ready",
"previous_status": "starting",
"operator_pool": "na",
"sku": "h200-141"
}
}
}Verifying the signature
Compute HMAC-SHA256(timestamp + "." + raw_body) with the endpoint secret. Compare in constant time. Reject events older than 5 minutes to defeat replay.
import crypto from "node:crypto";
export function verifyHoonifySignature(opts: {
secret: string;
payload: string; // raw request body, before JSON.parse
header: string; // value of "Hoonify-Signature"
}) {
const [tsPart, sigPart] = opts.header.split(",");
const t = tsPart.split("=")[1];
const v1 = sigPart.split("=")[1];
// Reject events older than 5 minutes.
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) {
throw new Error("stale event");
}
const expected = crypto
.createHmac("sha256", opts.secret)
.update(`${t}.${opts.payload}`)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) {
throw new Error("bad signature");
}
}Use the raw body
JSON.stringify drops or reorders whitespace and the HMAC will not match.Event types
| Type | Description |
|---|---|
| instance.lifecycle | Fires on every status transition. Includes previous_status. |
| instance.expired | Reservation reached its expiration. Instance is terminated automatically. |
| billing.alert | Org crossed an alert threshold (50/80/100%). Includes the threshold and current usage. |
| billing.invoice_finalized | Monthly invoice closed. Includes total, line items, and a download URL. |
| key.created | API key was created. Includes prefix + scopes (never the full secret). |
| key.rotated | API key was rotated. Includes overlap window end-time. |
| key.revoked | API key was revoked. Purge any cached credentials. |
| model.deprecation_announced | A model has a scheduled shutdown. Includes shutdown_at and a recommended replacement. |
| pool.degraded | A pool is routing-degraded. Re-check before pinning new traffic. |
| pool.recovered | A pool returned to nominal. |
Source IP allowlist
Outbound deliveries originate from the Hoonify webhook fleet. Allow these CIDRs through your ingress filter:
52.214.40.0/22 # NA
35.180.116.0/22 # EU
13.213.40.0/22 # APACRelated: Compute / instances · Authentication