Webhooks
Get an HTTP request the moment an appointment is created, confirmed, rescheduled, cancelled or completed, and connect BookifyLabs to your own systems.
Add an endpoint
- As an owner, open Settings in business scope and find the Webhooks card.
- Click Add endpoint and enter a public https:// URL. Private network addresses and localhost are refused.
- Copy the signing secret shown after creation. It starts with
whsec_and is displayed only once. - Send a test event by creating an appointment, then check the Deliveries list.
You can have up to 5 endpoints per business. Each can be paused and resumed, have its URL changed, its secret regenerated, or be deleted along with its delivery history.
Events
| Event | Sent when |
|---|---|
appointment.created | A booking is made, online, by Booky or by your team. |
appointment.confirmed | A pending appointment is confirmed. |
appointment.rescheduled | The time or professional changes. |
appointment.cancelled | The appointment is cancelled by you or the customer. |
appointment.completed | The appointment is marked done through the completion screen. |
Request format
Every delivery is a POST with a JSON body. The data object is a snapshot of the appointment at the moment the event happened; retries resend the same body.
POST https://example.com/hooks/bookify
Content-Type: application/json
User-Agent: BookifyLabs-Webhooks/1.0 (+https://bookifylabs.com)
X-Bookify-Event-ID: evt_3f6c…
X-Bookify-Timestamp: 1757145600
X-Bookify-Signature: 4b1d…
{
"id": "evt_3f6c…",
"type": "appointment.created",
"created_at": 1757145600,
"data": {
"appointment": { "id": 1234, "status": "booked", "start_at": "…", "…": "…" }
}
}Verify the signature
The signature is a hex HMAC-SHA256 over the string <timestamp>.<raw body>, keyed with your endpoint secret. Compute it from the raw request body, before any JSON parsing, and compare with a constant-time function.
import crypto from "node:crypto";
function verify(rawBody, headers, secret) {
const ts = headers["x-bookify-timestamp"];
const sig = headers["x-bookify-signature"];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // older than 5 minutes
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}- Reject timestamps older than 5 minutes to defeat replay.
- De-duplicate on X-Bookify-Event-ID: a retry carries the same id.
- Respond with any 2xx within 10 seconds. Do slow work after responding.
Retries and delivery history
A delivery that times out, or gets a 408, 425, 429 or any 5xx response, is retried every 12 hours for up to 30 attempts. Redirects are not followed and other 4xx responses are treated as final. The Deliveries table shows each event's status (pending, sending, retrying, delivered, failed), attempts, the response we stored (up to 4 KB) and the next attempt time. Failed or retrying deliveries can be pushed with Retry now. History is kept for 30 days.