Webhooks
Listen for events on your Bookingmood account so your integration can automatically trigger real-time reactions.
Why to use webhooks
When building integrations with Bookingmood, you might want your applications to receive events as they occur in your Bookingmood accounts, so that your backend systems can execute actions accordingly. To enable webhook events, you need to register webhook endpoints. After you register them, Bookingmood can push real-time event data to your application’s webhook endpoint when events happen in your Bookingmood account. Bookingmood uses HTTPS to send webhook events to your app as a JSON payload.
Receiving webhook events is particularly useful for listening to asynchronous events such as when a booking is registered, or when a payment is paid.
Using webhooks
When an event occurs, Bookingmood generates a new Event object. A single action might result in the creation of multiple events.
For example, if you confirm a booking, both the calendar_events.updated and calendar_events.confirmed events are emitted.
Event object
The Event object we send to your webhook endpoint provides a snapshot of the object that has been created or that has changed. The object has the following shape.
{
"id": "00000000-0000-0000-0000-000000000000",
"date": 1680064028,
"event_type": "calendar_events.updated",
"payload": {
"new": {
"id": "00000000-0000-0000-0000-000000000000",
...
},
"old": {
"id": "00000000-0000-0000-0000-000000000000",
...
}
}
}
The event_type field in the Event object indicates the type of event that occurred. The payload field contains the new and old versions of the object that has been created or that has changed.
Event types
Bookingmood emits these event types. Events ending in .created and .updated are sent when a record is created or changed; calendar_events.confirmed and calendar_events.cancelled when a booking's status changes, payments.paid when a payment is received, and tasks.completed when a task is marked as done.
bookings.createdbookings.updatedcalendar_events.createdcalendar_events.updatedcalendar_events.confirmedcalendar_events.cancelledcontacts.createdcontacts.updatedinvoices.createdinvoices.updatedpayments.createdpayments.updatedpayments.paidtasks.createdtasks.updatedtasks.completedmembers.createdmembers.updatedproducts.createdproducts.updatedsites.createdsites.updatedwidgets.createdwidgets.updated
How to set up your webhook integration
To start receiving webhook events in your app, you can follow the steps below. You can register one endpoint to handle several different event types at once, or set up individual endpoints for specific events.
Develop a webhook endpoint to receive events.
Register your endpoint in Bookingmood under Settings › Webhooks, or with the API.
Secure your webhook endpoint.
Delivery and retries
Bookingmood sends an event to your endpoint within seconds after it happens. A delivery succeeds when your endpoint responds with a 2xx or 3xx status code within 10 seconds. Redirects are not followed.
When a delivery fails, Bookingmood retries it with increasing delays: after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours, 24 hours and 24 hours. If all 9 attempts fail, over about 3 days, the event is marked as failed. You can see the deliveries of the last 7 days and the responses of your endpoint under Settings › Webhooks.
Disabled webhooks
When every attempt to deliver an event fails, and no other event reached the endpoint in the meantime, Bookingmood disables the webhook and emails the admins of your organization. A disabled webhook receives no new events. After fixing your endpoint, enable the webhook again under Settings › Webhooks, or set its disabled_at to null with the API.
1. Create a webhook endpoint function
Set up an HTTPS endpoint that can accept webhook requests with a POST method. Set up your endpoint function so that it:
Handles POST requests with a JSON payload consisting of an event object.
Quickly returns a successful status code (
2xx) prior to any complex logic that could cause a timeout. Bookingmood waits at most 10 seconds for a response.
Example endpoint
This code snippet is a webhook function configured to check that the event type was received, to handle the event, and return a 200 response.
// This example uses Express to receive webhooks
const express = require("express");
const app = express();
// Match the raw body to content type application/json
// If you are using Express v4 - v4.16 you need to use body-parser, not express, to retrieve the request body
app.post(
"/webhook",
express.json({ type: "application/json" }),
(request, response) => {
const event = request.body;
// Handle the event
switch (event.event_type) {
case "calendar_events.confirmed":
const calendarEvent = event.payload.new;
// Then define and call a method to handle the calendar event confirmation.
// handleCalendarEventConfirmation(calendarEvent);
break;
case "products.created":
const product = event.payload.new;
// Then define and call a method to handle the product creation.
// handleProductCreated(product);
break;
// ... handle other event types
default:
console.log(`Unhandled event type ${event.event_type}`);
}
// Return a response to acknowledge receipt of the event
response.json({ received: true });
}
);
app.listen(8000, () => console.log("Running on port 8000"));2. Register and manage your webhook in Bookingmood
After writing your webhook endpoint, register the webhook endpoint’s accessible URL under Settings › Webhooks or with the API so that Bookingmood knows where to deliver events. Registered webhook endpoints must be publicly accessible HTTPS URLs. To register your webhook endpoint, provide the publicly accessible HTTPS URL to your webhook endpoint, and select the type of events you’re receiving in your endpoint.
Manage a webhook endpoint configuration
You can always update or delete existing webhook endpoints under Settings › Webhooks or with the API.
3. Secure your webhooks
After confirming that your webhook endpoint connection works as expected, secure the connection by implementing webhook best practices.
One especially important best practice is to use webhook signatures to verify that Bookingmood generated a webhook request and that it didn’t come from a server acting like Bookingmood.
Event ordering
Bookingmood sends the events of a webhook in the order in which they’re generated, but doesn’t guarantee that they arrive in that order: an event that is retried arrives after later events. For example, paying a payment might generate the following events:
payments.updatedpayments.paid
Your endpoint shouldn’t expect delivery of these events in this order, and needs to handle delivery accordingly.
You can also use the API to fetch any missing objects (for example, you can fetch the related invoice using the information from payments.paid).
Handle duplicate events
Webhook endpoints might occasionally receive the same event more than once. You can guard against duplicated event receipts by making your event processing idempotent. One way of doing this is logging the events you’ve processed, and then not processing already-logged events.
Only listen to event types your integration requires
Configure your webhook endpoints to receive only the types of events required by your integration. Listening for extra events (or all events) puts undue strain on your server and we don’t recommend it. You can change the events that a webhook endpoint receives in the Dashboard or with the API.
Verify events are sent from Bookingmood
Use webhook signatures to confirm that received events are sent from Bookingmood, not by a third party. Bookingmood signs every event following the Standard Webhooks specification, with these headers:
| Header | Content |
|---|---|
webhook-id | The id of the event, the same for every delivery attempt |
webhook-timestamp | When the event was sent, in seconds since epoch |
webhook-signature | v1, followed by the base64-encoded HMAC-SHA256 signature |
The following section describes how to verify webhook signatures:
Retrieve your endpoint’s secret.
Verify the signature.
Retrieving your endpoint’s secret
Bookingmood generates a unique secret key for each endpoint. To retrieve it, go to Settings › Webhooks and copy the secret below the endpoint URL, or use the API. Additionally, if you use multiple endpoints, you must obtain a secret for each one you want to verify signatures on.
Verifying the signature
To verify the signature that Bookingmood sends with each event, perform the following steps:
Read the raw request body. Don't parse and serialize it again: the signature covers the exact bytes that were sent.
Prepare the signed content by joining the
webhook-idheader, thewebhook-timestampheader and the raw body with the character..Compute the expected signature as the HMAC-SHA256 of the signed content, with your
signing_secret(as UTF-8 text) as key, encoded as base64.Compare the expected signature to the signature after
v1,in thewebhook-signatureheader, using a constant-time comparison. If the signatures match, the event is verified.
The Standard Webhooks libraries can do this for you. They expect a base64-encoded secret, so pass your signing_secret encoded as base64.
Example of verifying signatures manually
// Set your secret key.
const signingSecret = "...";
// Make sure that the secret is never exposed to the client
// This example uses Express to receive webhooks
const express = require("express");
const crypto = require("crypto");
const app = express();
app.post(
"/webhook",
// Keep the raw body: the signature covers the exact bytes that were sent
express.raw({ type: "application/json" }),
(request, response) => {
const id = request.headers["webhook-id"];
const timestamp = request.headers["webhook-timestamp"];
const body = request.body.toString("utf8");
const expectedSignature = crypto
.createHmac("sha256", signingSecret)
.update(`${id}.${timestamp}.${body}`)
.digest("base64");
const signature = (request.headers["webhook-signature"] ?? "")
.split(" ")
.find((value) => value.startsWith("v1,"))
?.slice(3);
if (
!signature ||
signature.length !== expectedSignature.length ||
!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))
) {
response.status(401).send("Invalid signature");
return;
}
// Reject events that are more than 5 minutes old
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
response.status(401).send("Expired timestamp");
return;
}
const event = JSON.parse(body);
// Handle the event
switch (event.event_type) {
default:
console.log(`Received unhandled event type: ${event.event_type}`);
}
// Return a response to acknowledge receipt of the event
response.json({ received: true });
}
);
app.listen(8000, () => console.log("Running on port 8000"));Legacy signature
Events also carry an X-Signature header, for endpoints that verify events the way they did before the Standard Webhooks headers were added. It is \x followed by the hexadecimal MD5 hash of your signing_secret, the character . and the raw request body. Use the webhook-signature header for new integrations: MD5 is no longer considered secure.
Preventing replay attacks
A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them. To mitigate such attacks, each delivery includes a timestamp in the webhook-timestamp header and in the date field of the body (seconds since epoch). Because the timestamp is signed, an attacker can't change it without invalidating the signature.
If the signature is valid but the timestamp is too old, you can have your application reject the payload.
Bookingmood generates the timestamp and signature each time we send an event to your endpoint.
If Bookingmood retries an event (for example, your endpoint previously replied with a non-2xx status code), then we generate a new signature and timestamp for the new delivery attempt.
Azken aldaketa: 2026(e)ko azaroaren 3(a)