Skip to main content

Overview

Booking webhooks deliver a signed POST to an HTTPS endpoint you control every time a booking transitions in your workspace. Subscriptions are managed by a workspace administrator from the Copera app’s Bookings settings; each subscription has a receiver URL, a set of events, and a signing secret.

Events

EventFires when
booking.createdA booking is created (pending or confirmed).
booking.confirmedA booking is confirmed.
booking.cancelledA booking is cancelled.
booking.rescheduledA booking is rescheduled to a new time.
booking.declinedA pending booking is declined by the host.
booking.reassignedA booking’s host is reassigned (round-robin).
booking.no_showA no-show is recorded for the host or booker.
A subscription is delivered only for the events it includes, and only while it is active.

Request

Each delivery is a POST with a JSON body:
{
  "event": "booking.confirmed",
  "createdAt": "2026-07-06T09:00:00.000Z",
  "data": {
    "id": "665f…",
    "status": "CONFIRMED",
    "bookingTypeId": "665f…",
    "bookingTypeTitle": "Intro Call",
    "start": "2026-07-08T15:00:00.000Z",
    "end": "2026-07-08T15:30:00.000Z",
    "durationMinutes": 30,
    "timezoneAtBooking": "America/Sao_Paulo",
    "hosts": [{ "userId": "665f…", "role": "ORGANIZER" }],
    "booker": {
      "name": "Alice Booker",
      "email": "alice@example.com",
      "phone": "+15551234567",
      "timezone": "America/Sao_Paulo",
      "locale": "en"
    },
    "guests": [],
    "answers": [{ "questionId": "q1", "label": "Topic", "value": "Pricing" }],
    "location": { "type": "MEETING_CHANNEL" }
  }
}
The data block is your workspace’s own booking data, so the booker’s contact details are included. Internal handles (the booking’s manage token, idempotency key, ICS internals) are never sent.

Headers

HeaderValue
X-Copera-EventThe event string (e.g. booking.confirmed).
X-Copera-Signaturesha256=<hex> — HMAC-SHA256 of the raw request body using your subscription secret.
X-Copera-Webhook-VersionThe payload contract version (currently 1).

Verifying the signature

Compute the HMAC-SHA256 of the raw request body (before JSON parsing) with your subscription secret and compare it, in constant time, to the hex in X-Copera-Signature:
import { createHmac, timingSafeEqual } from "node:crypto";

function isValidSignature(rawBody, header, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
  const a = Buffer.from(header);
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}

Delivery and retries

  • Respond with a 2xx status to acknowledge receipt.
  • A 4xx response is treated as a permanent rejection — we do not retry.
  • A 5xx, network error, or timeout is retried with exponential backoff (3 attempts total). Receivers should be idempotent — the same event may be delivered more than once.
  • Receiver URLs must be public HTTPS endpoints; private / internal addresses are rejected.