# generationEvent

`generationEvent`

Event sent to your endpoint: `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

- `X-Relay-Key-ID` (string, Required)
- `X-Relay-Event-ID` (string, Required)
- `X-Relay-Timestamp` (string, Required)
- `X-Relay-Signature` (string, Required)

## Request body

- `billing_status` (string, Required) Whether Credits are held, charged or released.
- `closed_status` (string, Required)
- `created_at` (date-time, Required)
- `delivery_status` (string, Required) Whether the result is stored and downloadable. ready means it can be downloaded.
- `error_code` (string, Optional)
- `event_id` (string, Required)
- `execution_status` (string, Required) Progress of the model run.
- `free` (boolean, Required)
- `late_result_available` (boolean, Required) A result that arrived after closing is provided at no charge.
- `result` (object, Required, can be null) Delivered assets, once available.
  - `asset_id` (string, Required)
- `task_id` (string, Required)
- `type` (string, Required) Allowed values: `generation.succeeded`, `generation.failed`, `generation.canceled`, `generation.unresolved`, `generation.refunded`, `generation.late_result_available`
- `version` (integer, Required)

```json
{
  "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
}
```

## Responses

### 2XX Delivery accepted

```json
null
```

### cURL

```bash
# 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.json
```

### Python

```python
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

```javascript
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);
}
```
