Booking Webhooks
Booking webhooks deliver a signed POST to an HTTPS endpoint you control every time a booking transitions in your workspace — created, confirmed, cancelled, rescheduled, declined, reassigned, or flagged as a no-show. This page covers the event list, the payload, the headers, signature verification, and the delivery/retry contract. For the read-only Bookings API and a quick orientation, start with the Bookings introduction.
Webhook subscriptions are configured from the Copera app's Bookings settings by a workspace administrator — they are not created or managed through the Public API. Each subscription has a receiver URL, a set of events, and a signing secret.
Events
| Event | Fires when |
|---|---|
booking.created | A booking is created (pending or confirmed). |
booking.confirmed | A booking is confirmed. |
booking.cancelled | A booking is cancelled. |
booking.rescheduled | A booking is rescheduled to a new time. |
booking.declined | A pending booking is declined by the host. |
booking.reassigned | A booking's host is reassigned (round-robin). |
booking.no_show | A no-show is recorded for the host or booker. |
A subscription receives only the events it includes, and only while it is active.
Payload
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": "[email protected]",
"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
| Header | Value |
|---|---|
X-Copera-Event | The event string (e.g. booking.confirmed). |
X-Copera-Signature | sha256=<hex> — HMAC-SHA256 of the raw request body using your subscription secret. |
X-Copera-Webhook-Version | The 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
2xxto acknowledge receipt. - A
4xxis treated as a permanent rejection — Copera does not retry. - A
5xx, network error, or timeout is retried with exponential backoff (3 attempts total). Make your receiver idempotent — the same event may be delivered more than once. - Receiver URLs must be public HTTPS endpoints; private or internal addresses are rejected.
Reference
- Bookings introduction — orientation, Quick Start, parity, and the read-only Bookings API.
- Authentication — token scopes for the Bookings read endpoints.