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 kind
  • X-Caledee-Delivery-Attempt — 1 on the first try; bumped on each retry
  • X-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.