Integrations
Outbound webhooks
Subscribe an HTTPS endpoint to booking events. We sign every request with HMAC-SHA256, retry on transient failures, and auto-disable a stuck receiver after 15 consecutive failures.
Events you can subscribe to
booking_createdA new booking was just created.booking_canceledA booking was canceled — by the host or the invitee.booking_rescheduledAn existing booking was rescheduled to a new time. The data carries the new booking; the old one fires a separate booking_canceled with the reason "Rescheduled."
Request shape
Every request is a POST with a JSON body and these headers:
X-Caledee-Event— the event kindX-Caledee-Delivery-Attempt— 1 on the first try; bumped on each retryX-Caledee-Signature—t=<unix-ts>,v1=<hex-hmac>
{
"event": "booking_created",
"attempt": 1,
"sentAt": "2026-05-13T18:42:01.812Z",
"data": {
"id": "cmp4abc1230001",
"eventTypeId": "cmp2sbrbg000av8pnvpd051o7",
"eventTypeName": "30 minute meeting",
"hostId": "cmp2sbifg0000v8pnb5w2d2lt",
"hostSlug": "imad-bendimerad",
"startAt": "2026-05-20T14:00:00.000Z",
"endAt": "2026-05-20T14:30:00.000Z",
"durationMin": 30,
"status": "active",
"location": { "kind": "google_meet", "value": "https://meet.google.com/..." },
"invitee": { "name": "Alex", "email": "alex@example.com", "timezone": "Europe/Paris" },
"createdAt": "2026-05-13T18:42:01.250Z"
}
}Verifying the signature
Compute HMAC-SHA256(secret, `${ts}.${rawBody}`) and compare it (constant-time) to the v1 value in the signature header. Reject requests where the timestamp is more than 5 minutes off.
// Node.js — verify a Caledee webhook signature.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyCaledeeSignature({
rawBody, // request body as a string (don't JSON.parse first)
header, // request.headers["x-caledee-signature"]
secret, // the secret you copied when creating the webhook
toleranceSeconds = 300,
}) {
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("=").map((s) => s.trim())),
);
const ts = Number(parts.t);
const sig = parts.v1;
if (!ts || !sig) return false;
if (Math.abs(Date.now() / 1000 - ts) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(sig, "hex"));
}Retries and auto-disable
- Any 2xx response = success.
- 5xx, 408, 429, network errors, or timeouts (8s) trigger an exponential-backoff retry, up to 5 retries (≈ 8 minutes total).
- Other 4xx responses are permanent rejections — we mark the delivery failed and stop retrying.
- After 15 consecutive failures across all events we disable the subscription. Re-enable it from your settings.
Questions? hello@caledee.com.