---
updatedAt: 2026-07-17T13:24:17.000Z
---

Fetch the complete documentation index at: https://developers.zentag.ai/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Webhooks & signature verification

Receive signed event callbacks and verify their authenticity.

Zentag delivers signed HTTP POST callbacks when domain events occur. Register
endpoints in **Settings → Partner integrations → Webhooks** or via the
`webhooks:manage` scope.

## Event types

| Event               | When it fires                                      |
| ------------------- | -------------------------------------------------- |
| `clip.created`      | Clip row created (processing may still be running) |
| `clip.ready`        | Clip finished processing and is ready for delivery |
| `clip.failed`       | Clip processing failed                             |
| `highlight.ready`   | Highlight finished successfully                    |
| `highlight.failed`  | Highlight processing failed                        |
| `publish.succeeded` | Publish job completed                              |
| `publish.failed`    | Publish job failed                                 |
| `stream.started`    | Stream session started                             |
| `stream.ended`      | Stream session ended                               |

In the API Reference, see **Webhooks → Outbound event payload** for the shared
JSON schema (discriminate on `type`).

## Payload

```json
{
  "id": "evt_1a2b3c",
  "type": "clip.ready",
  "created_at": "2026-07-01T18:24:00.000Z",
  "data": { "object": "clip", "id": "clip_123", "status": "COMPLETED" }
}
```

## Headers

| Header             | Description                     |
| ------------------ | ------------------------------- |
| `Zentag-Signature` | `t=<unix>,v1=<hex HMAC-SHA256>` |
| `Zentag-Event`     | Event type                      |
| `Zentag-Event-Id`  | Unique event id (dedupe)        |
| `Zentag-Delivery`  | Delivery attempt id             |

## Verifying the signature

The signature is `HMAC-SHA256(signing_secret, "{timestamp}.{raw_body}")`. Compare
using a constant-time function and reject timestamps outside a \~5 minute window.

### Node.js

```js
import crypto from "node:crypto";

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

### Python

```python
import hashlib, hmac

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=") for p in header.split(","))
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
```

## Retries

Failed deliveries (non-2xx or timeout) are retried with exponential backoff
(\~1m, 2m, 4m, 8m, 16m, 32m). Always return `2xx` quickly and process
asynchronously. Use `Zentag-Event-Id` to make handling idempotent. You can
**replay** any delivery from the dashboard.