Documentation / Webhooks
Event statuses, pushed to you.
Zenix has no public API to poll. Instead, register an endpoint and every state change a run or account goes through is delivered to it.
01/Registering an endpoint
Add an endpoint in the console under Settings → Webhooks. You choose the URL and which event types it receives, and Zenix returns a signing secret shown once. Store it — you need it to verify deliveries, and it cannot be retrieved later.
Your endpoint must answer with a 2xx within 10 seconds. Anything else counts as a failed delivery.
02/Payload
Every delivery is a POST with a JSON body in this shape. data varies by event type; everything above it does not.
{
"id": "evt_01J9ZK4M2QX7",
"type": "run.completed",
"created": "2026-09-09T09:41:20Z",
"data": {
"run_id": "r-8842",
"account_id": "acc_vn21ab",
"network": "discord",
"attempted": 25,
"delivered": 25,
"failed": 0,
"duration_ms": 8140
}
}03/Event types
run.queuedA run was accepted and is waiting for a worker.run.startedA worker picked the run up and began dispatching.run.completedEvery attempt in the run finished. Carries the delivered and failed counts.run.failedThe run stopped before finishing. `reason` says why.run.heldHeld by policy — quiet hours, a rate window, or a concurrency cap.run.cancelledCancelled from the console before it finished.account.attachedA session was attached and resolved for the first time.account.invalidA health check found the session expired or revoked.account.challengedThe network issued a checkpoint or 2FA challenge.
04/Verifying a delivery
Each request carries a Zenix-Signature header: an HMAC-SHA256 of the raw request body, keyed with your endpoint secret, hex encoded. Compute it over the raw bytes — parsing and re-serialising the JSON will change them and the signature will not match. Compare with a constant-time function.
A Zenix-Timestamp header accompanies it. Reject deliveries older than five minutes to prevent replay.
import crypto from "node:crypto";
export function verify(rawBody: Buffer, header: string, secret: string) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(header, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}05/Delivery and retries
Failed deliveries retry with exponential backoff for 24 hours, then stop. Delivery is at least once: a network blip can produce the same event twice, so treat id as an idempotency key and ignore ones you have already handled.
Order is not guaranteed. Use created rather than arrival order when sequence matters. An endpoint that fails every delivery for 24 hours is disabled, and you are emailed.
No public API · Zenix does not expose a REST API for reading or driving runs. Work is defined in the console; webhooks are how you find out what happened.