Horato emits domain events after account sync, API writes, approvals, booking lifecycle changes, and operational jobs.
Signature verification
Each delivery is a POST with the JSON event as the body and two headers: `horato-event` (the event type) and `horato-signature` in the form `t=<timestamp>,v1=<signature>`. The signature is `HMAC-SHA256(secret, "<timestamp>.<raw_body>")`, hex-encoded, using the signing secret returned once when you create the webhook.
Verify over the raw request body before parsing JSON, compare signatures in constant time, and reject timestamps older than a few minutes to reduce replay risk. The legacy `x-okcal-event` / `x-okcal-signature` header names are still sent as aliases for consumers that verified against the pre-rebrand names.
import crypto from "node:crypto";
function verifyHoratoWebhook(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 ok = crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return ok && fresh;
}
verifyHoratoWebhook(rawBody, req.headers["horato-signature"], process.env.HORATO_WEBHOOK_SECRET);Event catalog
Subscribe a webhook to specific event types, or omit the filter to receive all of them. Events are namespaced by domain.
- Connections: `connection.reauth_required`.
- Email: `email.received`, `email.sent`, `email.draft.created`, `email.draft.updated`.
- Scheduling: `scheduling.booking.created`, `scheduling.booking.rescheduled`, `scheduling.booking.cancelled`.
- Recording: `recording.bot.started`, `recording.meeting.status_change`, `recording.transcript.updated`, `recording.recording.completed`.
- The complete, current list is in `/docs/openapi.json` and the events column of the API reference.
Delivery repair
Horato retries failed deliveries with exponential backoff and moves exhausted events to a dead-letter queue. Use the delivery inspector when a receiver returns a non-2xx status: fix the receiver, replay the delivery, then clear dead-letter queues intentionally.
- List deliveries with `/v1/webhooks/deliveries`.
- Replay one delivery with `/v1/webhooks/deliveries/{delivery_id}/replay`.
- Requeue dead-lettered events with `/v1/webhooks/dead-letter/{event_id}/requeue`.