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
{
"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
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
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.
Updated about 1 month ago