Type of event that triggered the notification
Handling Webhook Events API (1.9.0)
Webhook API specification for receiving real-time notifications from The Bookings Group system. Distributors must implement this endpoint to receive notifications about booking updates, inbound messages from operators via their Table Management System (TMS), and venue status changes.
eventType | Description | Populated fields |
|---|---|---|
booking.updated | A booking was created, updated, or cancelled | bookings |
message.received | One or more operator messages were sent for a booking | messages |
venue.updated | A venue's status changed. Today only closure is sent | venues |
webhook.ping | A connectivity test you asked for (POST /webhooks/test) | none |
Always switch on eventType and ignore values you do not recognise. New event types are added over time. A subscription created without an eventTypes list receives every event type, including new ones; rejecting an unfamiliar one causes delivery failures on your endpoint rather than preventing the event. A subscription with an eventTypes list receives only the types it names.
venue.updated is sent only to distributors that distribute the venue (an active relationship with the venue group, and the venue in their catalogue) and hold an active subscription for it.
A payment status change on a booking also fires booking.updated, but the payload carries no payment fields, so you will occasionally receive an update in which nothing visible changed. Treat it as a no-op.
POST /webhooks/test (Webhook API) sends any of these to your subscriptions on demand and reports what each endpoint answered. In Live it sends webhook.ping only; in the sandbox it also sends sample, real and simulated booking.updated, message.received and venue.updated events. Every test delivery carries an X-Webhook-Test: true header — a real event never does.
To test your message.received handling, use POST /webhooks/test with eventType: message.received: the message is built for the test only (never stored or emailed), the sender defaults to the booking's venue and can be overridden along with the subject, and a repeat is always delivered. In the sandbox, POST /bookings/{bookingId}/messages with direction: operator_to_partner (Messaging API) stores a real message and sends it too, but reports nothing about the delivery.
Every request carries:
X-API-Key— lowercase hex HMAC-SHA256 of the raw request body, keyed with thesecretKeyof the subscription it was sent for.X-Webhook-Id— that subscription'swebhookId. If you hold more than one subscription, use it to pick the secret.X-Webhook-Test: true— only on deliveries triggered byPOST /webhooks/test.
Verify the signature before processing the payload:
expected = HMAC-SHA256(key=SUBSCRIPTION_SECRET_KEY, message=raw_request_body).hex()
assert request.headers["X-API-Key"] == expectedReturn 204 No Content on success. Any non-2xx response, including a redirect (3xx, which is never followed), causes the delivery to be considered failed.
Respond within 10 seconds. venue.updated deliveries and test deliveries time out after 10 seconds per attempt; booking.updated and message.received deliveries allow 45 seconds in total, retries included.
A delivery that fails with 5xx, 408, 429, a timeout or a connection error is retried up to 3 times (once for venue.updated), waiting roughly 1–2, 2–4 and 4–8 seconds between attempts. On 429 we wait for your Retry-After, up to 10 seconds.
What happens after that depends on the event type:
eventType | After the immediate retries | Any other 4xx |
|---|---|---|
booking.updated | Redelivered, up to 5 deliveries in total, then parked for investigation | Treated as a failure and redelivered |
venue.updated | Redelivered, up to 5 deliveries in total, then parked for investigation | Final: recorded and not retried |
message.received | Not redelivered automatically; Bookable support can resend it | Not redelivered automatically |
A redelivery skips subscriptions that already answered 2xx. Delivery is at least once, so the same event can still arrive twice: deduplicate on the booking id and lastUpdate, or the message id.
Request
Endpoint that distributors must implement to receive notifications. The Bookings Group system sends HTTP POST requests to this endpoint when:
- A booking is updated (
booking.updated) - An operator sends a message via their TMS (
message.received) - A venue closes (
venue.updated)
Use eventType to determine which top-level field (bookings, messages or venues) is populated.
The webhookId of the subscription this delivery was sent for. Its secretKey signed the body.
ISO 8601 timestamp when the event was dispatched
Booking data. Present when eventType is booking.updated, omitted otherwise.
Message data. Present when eventType is message.received, omitted otherwise. All messages in a single notification belong to the same booking.
- Distributor webhook endpointhttps://your-distributor-domain.com
- curl
- JavaScript
- Node.js
- Python
- Java
- C#
- PHP
- Go
- Ruby
- R
- Payload
- bookingUpdated
- messageReceived
- venueUpdated
curl -i -X POST \
https://your-distributor-domain.com/ \
-H 'Content-Type: application/json' \
-H 'X-API-Key: YOUR_API_KEY_HERE' \
-H 'X-Webhook-Id: whk_3f2b8c1e5a7d4e9b9c0a1d2e3f4a5b6c' \
-H 'X-Webhook-Test: true' \
-d '{
"eventType": "booking.updated",
"timestamp": "2025-06-17T11:45:00Z",
"bookings": [
{
"id": "29|X9|275cc44dd2e2496fba44857c9257443a|B",
"compositeId": "29|X9|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b",
"date": "2025-06-25",
"time": "21:30:00",
"partySize": 6,
"status": "confirmed",
"reference": "REF-20250617-001",
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"phone": "+1234567890",
"duration": 120,
"productName": "dinner",
"productType": "dinner",
"notes": "Anniversary dinner — window table if possible",
"source": "MyDistributor",
"createdDate": "2025-06-17T09:15:00Z",
"lastUpdate": "2025-06-17T11:45:00Z"
}
]
}'