7 min read
Available onAvailabilityBooking
Listen for events on your Bookingmood account so your integration can automatically trigger real-time reactions.
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.
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.
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.
Bookingmood emits events for a variety of actions. The following table lists the event types that Bookingmood emits.
Event type | Description |
---|---|
bookings.created | A booking was created |
bookings.updated | A booking was updated |
calendar_event_tasks.created | A calendar event task was created |
calendar_event_tasks.updated | A calendar event task was updated |
calendar_event_tasks.completed | A calendar event task was completed |
calendar_events.created | A calendar event was created |
calendar_events.updated | A calendar event was updated |
calendar_events.confirmed | A calendar event was confirmed |
calendar_events.cancelled | A calendar event was cancelled |
contacts.created | A contact was created |
contacts.updated | A contact was updated |
invoices.created | An invoice was created |
invoices.updated | An invoice was updated |
payments.created | A payment was created |
payments.updated | A payment was updated |
payments.paid | A payment was paid |
members.created | A member was created |
members.updated | A member was updated |
products.created | A product was created |
products.updated | A product was updated |
sites.created | A site was created |
sites.updated | A site was updated |
widgets.created | A widget was created |
widgets.updated | A widget was updated |
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.
To account for unexpected events when processing the webhooks, we retry the webhook delivery up to 3 times. Between each try we wait one minute.
Set up an HTTPS endpoint that can accept webhook requests with a POST method. Set up your endpoint function so that it:
2xx
) prior to any complex logic that could cause a timeout.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"));
After writing your webhook endpoint, register the webhook endpoint’s accessible URL using the Webhooks section in your organization's settings or using 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.
You can always update or delete existing webhook endpoints using the Webhooks section on your organization's settings page or using the API.
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.
Bookingmood doesn’t guarantee delivery of events in the order in which they’re generated. For example, paying a payment might generate the following events:
payments.updated
payments.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
).
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.
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.
Use webhook signatures to confirm that received events are sent from Bookingmood. Bookingmood signs webhook events by including a signature in each event’s X-Signature
header. This allows you to verify that the events were sent by Bookingmood, not by a third party.
The following section describes how to verify webhook signatures:
Bookingmood generates a unique secret key for each endpoint. To retrieve the secret, use the Webhooks section on your organization's settings page and copy the secret below the endpoint URL. You can also use the API to retrieve the secret. Additionally, if you use multiple endpoints, you must obtain a secret for each one you want to verify signatures on.
To verify the signature that Bookingmood sends with each event, perform the following steps:
signing_secret
with the character .
and then the stringified request body.X-Signature
header. If the signatures match, the event is verified.// 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", express.json({ type: "application/json" }), (request, response) => { const signature = request.headers["x-signature"]; const signedPayload = `${signingSecret}.${JSON.stringify({ id: request.body.id, event_type: request.body.event_type, date: request.body.date, payload: request.body.payload })}`; const expectedSignature = crypto .createHash("md5") .update(signedPayload) .digest("hex"); if (signature !== expectedSignature) { response.status(401).send("Invalid signature"); return; } // Handle the event switch (event.type) { default: console.log(`Received unhandled event type: ${event.type}`); } // Return a response to acknowledge receipt of the event response.json({ received: true }); } ); app.listen(8000, () => console.log("Running on port 8000"));
A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them. To mitigate such attacks, Bookingmood includes a timestamp in the X-Signature
header. Because this timestamp is part of the signed payload, it’s also verified by the signature, so an attacker can’t change the timestamp without invalidating the signature.
If the signature is valid but the timestamp is too old, you can have your application reject the payload.
We suggest to tolerate at most 5 minutes between the timestamp and the current time.
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.
Last modified April 19, 2024
On this page
Why to use webhooksUsing webhooksEvent objectEvent typesHow to set up your webhook integrationRetry mechanisms1. Create a webhook endpoint functionExample endpoint2. Register and manage your webhook in BookingmoodManage a webhook endpoint configuration3. Secure your webhooksEvent orderingHandle duplicate eventsOnly listen to event types your integration requiresVerify events are sent from BookingmoodRetrieving your endpoint’s secretVerifying the secretExample of verifying signatures manuallyPreventing replay attacks