The event API has been designed to allow a merchant to take over customer communication, or extend its customer experience or process, by simply listening to events coming from Booxi. It could be used to create and send custom booking confirmation messages, or to share real-time booking status with another system, for example to greet a customer on a big screen, or to notify a clienteling app, applications are unlimited.
How it works
You simply register an event handler endpoint with our webhook API, and every time that event occurs, we call your handler endpoint, passing on all the information you need.
You can register multiple handler endpoints per event type, and the same handler endpoint for multiple event types.
Each handler endpoint is registered for a specific merchant group.
The event API can be used along with other account settings, such as disable sending email, or SMS, if you take over sending communication using the webhook.
Event Handler
You must create event handlers, expecting specific parameters to receive the event and its associated data set.
To register your handler endpoint, you must provide us with:
The URL of your endpoint
Tell us if you expect a POST or a PUT.
The list of event types (see below) to listen to.
Make sure you validate the unique event ID you receive per event, to avoid processing the same event twice. Booxi may send the same event twice on rare occasions.
If the webhook is configured to be sent for a group and a merchant, to the same URL, it will be sent twice, but using the same event Id.
If the webhook is configured to be sent for the same merchant, but multiple webhooks with different URLs, it will be sent to each URL, but again with the same event id.
If your endpoint requires oauth2 authentication using client credentials, you will also need to provide us the following:
The URL to your OAuth server's /token endpoint.
The client id and client secret to use when requesting the access token.
Optionally, a list of parameters to include when requesting the access token.
The name of the HTTP header to send the access token to (e.g. Authentication) and optionally, the authentication scheme to include in front of the access token (e.g. Bearer)
Handler Endpoint Security
Although the communication is done using SSL (HTTPS) and should always be a server-to-server call, we've implemented additional security measures to prevent attacks.
Adding a secret as a parameter
To ensure that only legitimate parties (i.e. Booxi) are calling your endpoint, you can add a secret parameter. The secret will be appended to the webhook URL as a query parameter. As such, the server will verify the secret before processing the request, ensuring that the incoming request is legitimate.
Adding a secret in the header
You may also request to add a secret in the header (var_name:value) of each call to your endpoint; this operates in the same way as the parameter mentioned above.
Ex: {“secret”:”abcde12345”}
Verifying a signature
We can add a signature and timestamp in a JSON payload we send back to your endpoint, which you can compute and compare (using a shared secret) to ensure that the payload was not modified by a middleman.
Since each event has a unique id (included in the signed data), this also protects you from repeat attacks.
Webhook Handler Endpoint
You can use any language to create your handler endpoint. Expect to receive the Event JSON object in parameter. The endpoint must return a 200 code; if it doesn't (for example, returning a 400 or 500 code), Booxi will retry up to 30 times.
See here for the various object types:
Standard codes:
Language codes : ISO 639-3
Currency codes: ISO 4217
Country codes: ISO 3166-1 alpha-2
Time is in Zero Offset format (so it must be adjusted to the store time zone)
Considerations:
The endpoint URL must use HTTPS.
The webhook will not follow redirects.
The webhook can be configured to either POST or PUT.
The webhook can be configured to send only a subset of the event types.
The endpoint should return HTTP 200. Any other response will cause a retry.
The endpoint must reply within 10 seconds (which can be configured). A timeout will cause a retry.
The system will retry failed webhook events with an exponential backoff for up to 1 day.
The system can send multiple webhook events at the same time.
The events are sent as they are generated, but this does not guarantee they will be received in order.
The events contain a version number. It will only be increased if the webhook model changes in a backward incompatible way.
Oauth 2 authentication is available (optional).
Webhook Handler Endpoint Example
{
"id": 243999,
"type": "appointment.created",
"createdOn": "2024-08-20T13:06:37Z",
"sentOn": "2024-08-20T13:06:37Z",
"staffs": [
{
"id": 155304,
"email": "john@gmail.com",
"language": "eng",
"lastName": "Jones",
"position": "Owner",
"biography": "",
"firstName": "John",
"canReplyTo": true,
"translations": [
{
"language": "eng",
"position": "Owner"
}
],
"profileImageUrl": ""
}
],
"booking": {
"items": [
{
"price": {
"tax": "None",
"amount": "0.50",
"visibility": "Show",
"isStartingAt": false,
"amountPerHour": "0.00",
"amountPerPerson": "17.34"
},
"serviceId": 376894,
"serviceName": "Price per person",
"reservedStaffTimespan": {
"end": "2024-08-21T10:30:00Z",
"start": "2024-08-21T10:00:00Z",
"duration": 30
},
"reservedClientTimespan": {
"end": "2024-08-21T10:30:00Z",
"start": "2024-08-21T10:00:00Z",
"duration": 30
}
}
],
"client": {
"id": 2376756,
"email": "mary@gmail.com",
"lastName": "Smith",
"presence": "Expecting",
"timeZone": "",
"firstName": "Mary",
"remindBySMS": false,
"trackingPage": {
"viewUrl": "https://site.booxi.com/tracking/VG62IW0O7-iy_P89a2tDX9KmwM9CN3i18dAr",
"canCancel": true,
"canModify": true,
"canConfirm": false
},
"remindByEmail": false,
"homePhoneNumber": "+399907302536",
"additionalRequest": "",
"hasRequestedStaff": true,
"mobilePhoneNumber": ""
},
"status": "Approved",
"payment": {
"paid": "0.00",
"taxes": [
{
"name": "Tax 1",
"rate": "0.033400",
"amount": "0.00"
},
{
"name": "Tax 2",
"rate": "0.052100",
"amount": "0.00"
}
],
"total": "17.84",
"subtotal": "17.84",
"onlinePaymentStatus": "None"
},
"staffId": 155304,
"location": "Business",
"createdBy": "Client",
"createdOn": "2024-08-20T13:06:37Z",
"quickNote": "",
"merchantId": 11999,
"modifiedBy": "Client",
"modifiedOn": "2024-08-20T13:06:37Z",
"clientCount": 1,
"isCompleted": false,
"acquisitionChannel":"e-commerce",
"isScheduled": true,
"locationText": "",
"bookingMethod": "Appointment",
"confirmationNumber": "A00343644",
"totalStaffTimespan": {
"end": "2024-08-21T10:30:00Z",
"start": "2024-08-21T10:00:00Z",
"duration": 30
},
"clientCommunication": "ClientOnly",
"totalClientTimespan": {
"end": "2024-08-21T10:30:00Z",
"start": "2024-08-21T10:00:00Z",
"duration": 30
}
},
"clients": [
{
"id": 2376756,
"email": "mary@gmail.com",
"gender": "Unknown",
"address": {
"city": "Dallas",
"state": "",
"street": "1 Main Ave",
"country": "US",
"postalCode": ""
},
"language": "eng",
"lastName": "Smith",
"createdOn": "2024-06-19T23:45:28Z",
"firstName": "Mary",
"modifiedOn": "2024-08-20T13:06:37Z",
"remindBySMS": false,
"inMailingList": false,
"remindByEmail": false,
"homePhoneNumber": "+399907302536",
"membershipNumber": "",
"mobilePhoneNumber": "+15145555555"
}
],
"surveys": [],
"merchant": {
"id": 11999,
"name": "Westhaven",
"email": "adam@gmail.com",
"address": {
"city": "Paris",
"state": "",
"street": "30 avenue Montaigne",
"country": "TH",
"postalCode": "75017"
},
"groupId": 1859,
"currency": "EUR",
"timeZone": "Europe/Paris",
"bookingUrl": "https://site.booxi.com/westhavenstore",
"websiteUrl": "",
"description": "",
"phoneNumber": "+2348039013999",
"locationCode": "3624",
"ownerStaffId": 155304,
"translations": [
{
"language": "eng",
"bookingTermsUrl": "https://custom.terms.com/",
"bookingPrivacyUrl": "https://custom.privacy.com/es"
}
],
"bookingTermsUrl": "https://custom.terms.com/",
"defaultLanguage": "fra",
"profileImageUrl": "",
"bookingPrivacyUrl": "https://custom.privacy.com/es"
},
"services": [
{
"id": 376894,
"name": "Price per person",
"tags": "",
"price": {
"tax": "None",
"amount": "0.50",
"visibility": "Show",
"isStartingAt": false,
"amountPerHour": "0.00",
"amountPerPerson": "17.34"
},
"location": "Business",
"categoryId": 53568,
"description": "",
"instruction": "",
"locationText": "",
"showDuration": true,
"bookingMethod": "Appointment",
"profileImageUrl": "",
"durationForStaff": 30,
"durationForClient": 30,
"externalSurveyUrl": "",
"showStaffNameOnline": true
}
],
"resources": [],
"clientLinks": [],
"serviceCategories": [
{
"id": 53568,
"name": "Manicure",
"description": "",
"translations": [
{
"name": "Manicure",
"language": "eng"
}
],
"profileImageUrl": ""
}
],
"consentActions": [
{
"clientId": 2376756,
"consentId": 700,
"consentVersionId": 850,
"action": "Grant",
"on": "2024-08-01T07:15:00Z",
"bookingId": "A00155304",
"bookingMerchantId": "15229"
"proxy: "None",
"usingIP": "127.0.0.1",
"usingUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
"usingApp": "BookNow"
}
],
"consents": [
{
"id": 850,
"versionId": 700,
"language": "eng",
"name": "Data Retention Policy",
"text": "Do you agree to this [policy](https://www.booxi.com/policy)?"
}
]
}
Example of client consent change
{
"id": 21506,
"type": "client.consent.changed",
"createdOn": "2020-01-01T08:00:01Z",
"sentOn": "2020-01-01T08:00:02Z",
"content": {
"clients": [
{
"id": 400,
"firstName": "Carmen",
"lastName": "David",
"email": "carmen.david@booxi.com",
"homePhoneNumber": "+15552346789",
"mobilePhoneNumber": "+15552346789",
"presence": "Expecting",
"remindByEmail": true,
"remindBySMS": true,
"additionalRequest": "",
"hasRequestedStaff": true,
"isAttending": true,
"address": {
"street": "123 Fake St",
"city": "New York",
"state": "NY",
"postalCode": "10001",
"country": "US"
},
"trackingPage": {
"viewUrl": "https://site.booxi.com/html/tracking.html?tracking_number=unique-tracking-number&from=carmen.davis@booxi.com&validation=87547",
"canConfirm": false,
"canModify": false,
"canCancel": true
},
"timeZone": "America/New_York"
}
],
"consentActions": [
{
"clientId": 400,
"consentId": 700,
"consentVersionId": 850,
"action": "Grant",
"on": "2024-08-01T07:15:00Z",
"bookingId": "A00155304",
"bookingMerchantId": "15229"
"proxy: "Staff",
"usingIP": "127.0.0.1",
"usingUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
"usingStaffId": 205,
"usingApp": "BackOffice"
}
],
"consents": [
{
"id": 700,
"versionId": 850,
"language": "eng",
"name": "Data Retention Policy",
"text": "Do you agree to this [policy](https://www.booxi.com/policy)?"
}
]
}
}
Example of a "client notification"
{
"id": 7,
"type": "client.notification.sent",
"content":
{
"clientNotification":
{
"clientId": 145637,
"communicationMethod": "Email" // VoiceCall, SMS, Email
"email": "demo@booxi.com"
"mobilePhoneNumber": "",
"message": "This is a custom notification sent to the client."
}
}
}
Signature Validation
Webhook event HTTP requests can be signed to increase security and prevent middleman attacks. When configured with a secret_token, the HTTP request includes a signature to validate that the event was generated by Booxi and not modified before it reaches its destination.
The signature is available as an HTTP header called Booxi-Signature.
It is an HMAC built from the HTTP body, the secret token and the SHA256 hash function.
It is encoded as hexadecimal digits.
Here are examples on how to compute the signature, in order to verify it.
Node.js
const crypto = require("crypto");
const signature crypto.createHmac("sha256", secret_token).update(http_body).digest("hex");
PHP
$signature = hash_hmac('sha256', $http_body, $secret_token);
Python
import hmac
import hashlib
signature = hmac.new(secret_token, http_body, hashlib.sha256).hexdigest()
When computing the signature, it's important to pass the request body as it was received. Make sure that the string encoding was not changed and that the JSON parsing is done after.
Events
Here is the list of event types you can listen to (i.e. register event handler endpoints for). There are 2 booking types (i.e. appointments and group event reservations) each with its own event types you can listen to.
Appointment Events
These are triggered by appointments. Use the associated event types to register your handler endpoints.
Webhook Event Type | Event Triggers (and changed values) |
appointment.created |
booking.confirmationNumber |
appointment.status_updated | Status is changed to:
booking.status *The rest of the appointment must not have changed. |
appointment.updated |
booking.staffId |
appointment.updated |
booking.isScheduled |
appointment.updated |
booking.clientAvailabilities[].start *The requested client availability is only visible for unscheduled appointment requests. |
appointment.updated |
appointment.items[].resourceId |
appointment.updated |
booking.items[].serviceId |
appointment.updated |
booking.location |
appointment.updated |
booking.clientCount |
appointment.remind |
*Even if the client does not want to receive any reminder. This event is triggered when the reminder or additional reminder is due. |
appointment.recall |
recall.serviceId *There are no events when the recall is created or cancelled. |
appointment.online_payment |
appointment.payment.onlinePaymentAmount |
The 'totalClientTimespan' will be 0 if the appointment is not yet scheduled.
Reservation Events
These are triggered by group event reservations. Use the associated event types to register your handler endpoint.
Webhook Event Type | Event Triggers (and changed values) |
reservation.created |
booking.confirmationNumber |
reservation.status_updated |
booking.status Group reservation is completed* booking.isCompleted *The rest of the reservation must not have changed. |
reservation.updated |
booking.items[].price.amount |
reservation.updated |
booking.clientCommunication |
reservation.updated |
booking.client.id |
reservation.updated |
booking.attendees[].clientId |
reservation.remind |
(no related data)
* Even if no attendee wants to receive any reminder * Reminder time set when group (not reservation) is created or modified
This event is triggered when the reminder or additional reminder are due. |
reservation.online_payment |
reservation.payment.onlinePaymentAmount |
Closing a group will send completed events for each reservation, which is used to send Thank You emails; however, if the user closes, re-opens, and closes again, clients will not receive the thank you email again.
Client Notification Events
These are triggered by the notify client feature to send custom notification to the client by email, SMS, or record a phone call that was made. Use the associated event types to register your handler endpoint.
Webhook Event Type | Notification Operation |
client.notification.sent |
clientNotification.clientId |
Client Consent Change
This event is triggered whenever a client grants or revokes a consent.
Webhook Event Type | Consent Operation |
client.consent.changed |
Actions that trigger this event:
|
Test
To test your event handler endpoints, simply provide us the required information, so we can register those endpoints. You can also use tools like requestbin.com to monitor what we send you when we call an endpoint.
You can create a test account using
Login to your test account using:
app.booxi.com (USA hosting)
app.booxi.eu (EU hosting)
Test your handler by booking appointments and reservations using Booxi.
Using webhooks to send notifications
This section explains which events and information to look for to send the associated notification by email or SMS.
Notification content:
client - provides the name, email and mobile of the customer to send the message to
booking - provides the current booking details
The date and time of the appointment
The service booked
The staff name who will provide the service
The address where the service will be provided
previous booking - provides the previous booking details in case of changes
additionalRequest - may contain data pushed with the appointment, or additional information provided by the customer
trackingPage - provides unique urls to view or modify appointments, which can be used to allow the customer to make changes if the booking rules of the associated service allows it.
Actions that can be performed with each a url
Make sure to pass the client preferred language parameter to the url in order to open the tracking page in the right language. Add &lang=eng for English.
trackingQRCodeUrl - provides a unique url to a QR image that represents this booking/client, as well as the QR code for booking/attendee.
staffVideoConferenceUrl - if the location is set to video conference, this will be the link to join as a staff
Links to join as the client and the attendees will be found here: client.videoConferenceUrl, attendees[].videoConferenceUrl
translations - Some content (objects) may be provided in various languages for which the translations can be found in the associated objects: Staff, Service, Resources, Merchant.
Each object has a translations property that contains the additional language and its translated content.
The data is provided by default in the Merchant default language (defaultLanguage), which means that if the client preferred language (language) does not match the merchant default language, you must use the translations.
Do not send Messages
In some cases, you should not send any messages. This is applicable when a staff selects the option to not send a booking update notification to the client, or when a client books online and chooses to not send notifications to the attendees.
Check the value of content.booking.clientCommunication:
DoNotSend: Should not send any message to the client or attendee of this booking
ClientOnly: Should only send messages to the client of the booking.
ClientAndAttendees: Should send messages to both the client and the attendees of this booking.
Booking Tracking Page URL
Each booking comes with a URL to view the booking online and optionally modify it. You can add parameters to the URL to automatically trigger an action based on a CTA in your email.
Use the content.booking.trackingPage.viewURL value to open the tracking page
Make sure to add lang=XXX to this URL to open “Book Now” in the customer preferred language.
Add action=modify to trigger the modify CTA
Add action=cancel to trigger the cancel CTA
Add action=confirm to trigger the confirm CTA
Check the content.booking.trackingPage.canConfirm, canModify, canCancel value to know which CTA will be available for that booking.
*Note: a tracking page URL will not be created if the client has no email address.
Staff Messages
Events for staff messages are the same as the clients'. For instance, when a confirmation message is sent to the client, a message can also be sent to the staff from the same appointment.created event with booking.status=Approved. Booxi only creates or updates an appointment once and sends one event. However, you can perform multiple actions or send multiple messages for the single event received.
Booxi currently sends the following messages to staff:
New appointment booked for the staff
By the client
By the staff himself
By another staff
New appointment approved
New appointment booked with flexible dates
New appointment booked needs to be manually dispatched (no staff selected)
Appointment has changed
By the client
By the staff (i.e. the one who changed the appointment)
By another staff
Appointment has been canceled
By the client
By the staff (i.e. the one who cancelled the appointment)
By another staff
Appointment Confirmation
Send a confirmation email when a new appointment is created or approved. In manual approval mode, an appointment can be created pending a manual approval by the staff, and the confirmation will only be sent once the appointment is approved.
Webhook events to look for:
event type=appointment.created and booking.status=Approved
event type=appointment.status_updated and booking.status=Approved with client.presence=Expecting
Look at previous.status != booking.status to detect the status change
event type=appointment.updated, and booking.status=Approved and previous.status != booking.status to detect the status change
It is possible that an appointment update (not just the status), also updated the status at the same time, so you must also verify the status change from an appointment.update event.
Actions:
trackingPage.viewURL
trackingPage.canModify (boolean indicating that modify is allowed)
trackingPage.canCancel (boolean indicating that cancel is allowed)
Appointment request notification
Send a notification informing that the customer’s appointment request was received, and will later be approved by the staff.
Webhook events to look for:
event type=appointment.created and booking.status=PendingStaffApproval
Actions:
trackingPage.viewURL
trackingPage.canCancel (boolean indicating that cancel is allowed)
Appointment changed and awaiting client to confirm changes
Send a notification informing that the appointment was modified by a staff member, requiring the customer to confirm the changes are OK.
Webhook events to look for:
event type=appointment.created and booking.status=PendingClientApproval
Actions:
trackingPage.viewURL
trackingPage.canConfirm (boolean indicating that confirm is allowed)
trackingPage.canModify (boolean indicating that modify is allowed)
trackingPage.canCancel (boolean indicating that cancel is allowed)
Appointment changed notification
Send a notification informing that the appointment was modified by a staff member. To understand which changes were made, you can compare the booking data with the previous booking data.
Webhook events to look for:
event type=appointment.updated
We recommend to send a change notification only when one of the following changes occurs:
The date/time
The staff requested by the client
The service location
Actions:
trackingPage.viewURL
trackingPage.canModify (boolean indicating that modification is allowed)
trackingPage.canCancel (boolean indicating that cancel is allowed)
Appointment canceled by the store
Send a notification informing that the appointment was modified by a staff member.
Webhook events to look for:
event type=appointment.status_updated and booking.status=Cancelled, modifiedBy=staff
Actions:
trackingPage.viewURL
Appointment canceled by the client
Send a notification informing that the appointment is now canceled, and was canceled by the client. This message allows you to differentiate between a confirmation of cancellation by the client and a booking that was canceled by the store (see previous case above).
Webhook events to look for:
event type=appointment.status_updated and booking.status=Cancelled, modifiedBy=client
Actions:
trackingPage.viewURL
Appointment is a no-show
Send a notification informing the customer of their absence (i.e. no-show) and invite the them to book again.
Webhook events to look for:
event type=appointment.status_updated and booking.status=Approved, client.presence=NoShow
Actions:
trackingPage.viewURL
Appointment is completed
Send a thank you email to the customer once the appointment is completed. This email is automatically sent (if the option is enabled in the "Business rules" tab) once the appointment is completed.
Webhook events to look for:
event type=appointment.status_updated and booking.isCompleted=true
To send a thank you message, also make sure the isCompleted has changed by comparing with the previousBooking.isCompleted
Appointment reminder
Send a reminder notification to the customer before the appointment. The delay before the appointment can be configured in the "Business Rules" tab. See client.remindBySMS and client.remindByEmail to select the preferred communication method of the customer to send the reminder.
Webhook events to look for:
event type=appointment.remind
Actions:
trackingPage.viewURL
Appointment recall
Send a reminder to book a new appointment sometimes after the last appointment was completed. Booxi has a reminder system that will program a recall message X days after an appointment is completed. This option must be activated in Booxi.
Webhook events to look for:
event type=appointment.recall
Reservation is completed
Send a thank you email to the customer once the group reservation is completed. Booxi automatically sends an email (if the Thank You Message option is enabled in the "Business rules" tab) once the group is completed, and auto completion of groups can be configured in Booxi.
Webhook events to look for:
event type=reservation.status_updated and booking.isCompleted=true
To send a thank you message, also make sure the isCompleted has changed by comparing with the previousBooking.isCompleted
Using webhooks to capture consents
Here are the events and information to look for when capturing consents.
To capture consents, register a webhook callback listening to the events mentioned below, and update your consent information database using the provided values.
Webhook events to look for:
event type=appointment.created
event type=appointment.updated
event type=appointment.status_updated
Relevant variables:
"consentActions"
"consents"
ConsentAction object
Property | Example value | Description |
clientId | 399 | The id of the client for whom the consent was changed. |
consentId | 700 | The id of the consent, which can be used to retrieve its details. |
consentVersionId | 8500 | The exact version of the consent. |
action | "Grant" | The consent action performed. Possible values are: Grant or Revoke |
on | "2024-08-20T13:06:37Z" | The date/time at which the consent was given or revoked (UTC). |
bookingId | "A00001234" | The booking ID associated with the consent. |
bookingMerchantId | "15229" | Merchant ID associated with the booking where the consent was captured. |
proxy | "None" | If a proxy entity was used to give consent. Possible values:
|
usingIP | "127.0.0.1" | The IP address from which the consent action was performed (see note 1 below). |
usingUserAgent | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36" | The user agent or browser that was used to perform the consent action. |
usingStaffId
| 14325 | The ID of the user who created the booking (see note 2 below); only set if "proxy" is set to "Staff". |
usingApp | "BackOffice" | From where the consent was given or revoked. Options include: BackOffice, TrackingPage, BookNow, API |
Consent object
Property | Example value | Description |
id | 700 | The id of the consent, which can be used to retrieve its details. |
versionId | 850 | The exact version of the consent. |
language | "eng" | The language of the consent. |
name | "Data Retention Policy" | The name of the consent. |
text | "Do you agree to this [policy](https://www.booxi.com/policy)?" | The text content of the consent. |
The IP address may not be a completely reliable data point, since clients may use a VPN (which changes the user’s IP address) when granting/revoking their consents.
When a Booxi user makes a booking on behalf of the customer, all mandatory consents are implied; in this case, the IP address included in the consent record will be associated with the Booxi user that performed the action. You can easily tell when this occurred by looking at the "proxy" property; when it's set to "Staff", this means the action was done by the staff on behalf of the client (look at the "usingStaffId" value for the staff's ID).