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

HeaderValue
Hoonify-EventThe event type (also in the body). Useful for routing without parsing.
Hoonify-Event-IdStable event ID. Use for dedup if your handler is not naturally idempotent.
Hoonify-Signaturet=<unix>,v1=<hmac-sha256>. See verification below.
Hoonify-DeliveryPer-attempt UUID. Increments per retry of the same event.

Example payload

json
{
  "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.

typescript
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

Sign the bytes before any framework JSON-parses them. Re-serializing through JSON.stringify drops or reorders whitespace and the HMAC will not match.

Event types

TypeDescription
instance.lifecycleFires on every status transition. Includes previous_status.
instance.expiredReservation reached its expiration. Instance is terminated automatically.
billing.alertOrg crossed an alert threshold (50/80/100%). Includes the threshold and current usage.
billing.invoice_finalizedMonthly invoice closed. Includes total, line items, and a download URL.
key.createdAPI key was created. Includes prefix + scopes (never the full secret).
key.rotatedAPI key was rotated. Includes overlap window end-time.
key.revokedAPI key was revoked. Purge any cached credentials.
model.deprecation_announcedA model has a scheduled shutdown. Includes shutdown_at and a recommended replacement.
pool.degradedA pool is routing-degraded. Re-check before pinning new traffic.
pool.recoveredA pool returned to nominal.

Source IP allowlist

Outbound deliveries originate from the Hoonify webhook fleet. Allow these CIDRs through your ingress filter:

text
52.214.40.0/22       # NA
35.180.116.0/22      # EU
13.213.40.0/22       # APAC

Related: Compute / instances · Authentication