generationEvent
Verify Base64(HMAC-SHA256(signing secret, timestamp + '.' + event_id + '.' + raw_body)) with a constant-time comparison and a five-minute Unix timestamp window. Deduplicate by event_id. Redelivery preserves the body and event_id.
VizoAPI sends this request to the webhook endpoint you create in the console.
Request headers
| Field | Description |
|---|---|
| X-Relay-Key-IDstringRequired | |
| X-Relay-Event-IDstringRequired | |
| X-Relay-TimestampstringRequired | |
| X-Relay-SignaturestringRequired |
Request body
A JSON object. Fields that are not listed are rejected.
| Field | Description | ||
|---|---|---|---|
| billing_statusstringRequired | Whether Credits are held, charged or released. | ||
| closed_statusstringRequired | |||
| created_atdate-timeRequired | |||
| delivery_statusstringRequired | Whether the result is stored and downloadable. ready means it can be downloaded. | ||
| error_codestringOptional | |||
| event_idstringRequired | |||
| execution_statusstringRequired | Progress of the model run. | ||
| freebooleanRequired | |||
| late_result_availablebooleanRequired | A result that arrived after closing is provided at no charge. | ||
| resultobject, can be nullRequired | Delivered assets, once available. Fields of result
| ||
| task_idstringRequired | |||
| typestringRequired | generation.succeededgeneration.failedgeneration.canceledgeneration.unresolvedgeneration.refundedgeneration.late_result_available | ||
| versionintegerRequired |
Responses
2XX Delivery accepted
Request example
cURL
# Your endpoint receives a POST. Replay one locally to test your handler:
curl -X POST "$YOUR_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-H "X-Relay-Key-ID: $KEY_ID" \
-H "X-Relay-Event-ID: $EVENT_ID" \
-H "X-Relay-Timestamp: $TIMESTAMP" \
-H "X-Relay-Signature: $SIGNATURE" \
--data-binary @event.jsonPython
import base64, hashlib, hmac, time
def verify(secret: bytes, headers: dict, raw_body: bytes) -> bool:
ts = headers["X-Relay-Timestamp"]
if abs(time.time() - int(ts)) > 300:
return False # outside the five-minute window
msg = ts.encode() + b"." + headers["X-Relay-Event-ID"].encode() + b"." + raw_body
digest = base64.b64encode(hmac.new(secret, msg, hashlib.sha256).digest()).decode()
return hmac.compare_digest(digest, headers["X-Relay-Signature"])Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, headers, rawBody) {
const ts = headers["x-relay-timestamp"];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // outside the five-minute window
const msg = `${ts}.${headers["x-relay-event-id"]}.${rawBody}`;
const want = createHmac("sha256", secret).update(msg).digest();
const got = Buffer.from(headers["x-relay-signature"], "base64");
return got.length === want.length && timingSafeEqual(got, want);
}{
"billing_status": "held",
"closed_status": "string",
"created_at": "2026-11-02T08:01:43Z",
"delivery_status": "pending",
"error_code": "string",
"event_id": "string",
"execution_status": "queued",
"free": false,
"late_result_available": false,
"result": null,
"task_id": "string",
"type": "generation.succeeded",
"version": 1
}