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 listener endpoint with our webhook API, and every time that event occurs, we call your listener endpoint, passing on all the information you need.
You can register multiple listener endpoints per event type, and the same listener endpoint for multiple event types.
Each listener endpoint is registered for a specific merchant group.
The event API can be used along with other account settings such as disable sending email and/or SMS (i.e. you prefer all communication be sent using the webhook, not through Booxi).
Event Listener
You must create event listeners, expecting specific parameters to receive the event and its associated data set.
*Note: The total number of listener endpoints that can be registered is limited per merchant group; contact your Booxi representative for more info.
To register your listener 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)
Listener 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 Listener
You can use any programming language to create your listener endpoint. Expect to receive the Event JSON object in the response. 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 Event Payload 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 listener 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 listener 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 listener 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 listener 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:
|
Testing
To test your event listener 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 account using:
app.booxi.com (USA hosting)
app.booxi.eu (EU hosting)
Test your listener by booking appointments and reservations using Booxi.
Using webhooks to send notifications
If you want to use webhooks to send notifications (i.e. integrate with a marketing or a notification platform), see here.
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/privacy-legal#privacy-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).