Skip to main content

Event API Webhook Implementation Guide

Updated this week

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

  • New appointment is created by staff or client

  • New appointment requested (no staff / no date) by client

booking.confirmationNumber

appointment.status_updated

Status is changed to:

  • Approved/Canceled/ClientArrived/NoShow/Completed/Reopen by staff*/Requested

  • Status is confirmed or cancelled by the client*

  • Status is automatically approved after delay*

  • Status is automatically canceled after a missing payment*

booking.status
booking.isCompleted
Booking.client.presence

*The rest of the appointment must not have changed.

appointment.updated

  • Staff is set, changed or removed

booking.staffId

appointment.updated

  • Date is set, changed or removed

booking.isScheduled
booking.totalClientTimespan.start
booking.totalStaffTimespan.start

appointment.updated

  • The client's requested availability (i.e. client availability) changed*

booking.clientAvailabilities[].start
booking.clientAvailabilities[].end

*The requested client availability is only visible for unscheduled appointment requests.

appointment.updated

  • Resource is set, changed or removed

appointment.items[].resourceId

appointment.updated

  • Service is added, removed, reordered or changed

booking.items[].serviceId
booking.items[].reservedStaffTimespan.start
booking.items[].reservedStaffTimespan.duration
booking.items[].reservedClientTimespan.start
booking.items[].reservedClientTimespan.duration
booking.items[].price.amount
booking.items[].price.amountPerPerson
booking.items[].price.amountPerHour
booking.items[].price.show
booking.items[].price.tax
booking.items[].price.type
booking.items[].surveyResponse

appointment.updated

  • Appointment location is changed

booking.location
booking.location_details (if PHONE, VIDEO, CUSTOM)

appointment.updated

  • Appointment client is changed

booking.clientCount
booking.client.id
booking.client.firstName
booking.client.lastName
booking.client.email
booking.client.homePhoneNumber
booking.client.mobilePhoneNumber
booking.client.remindByEmail
booking.client.remindBySMS
booking.client.additionalRequest
booking.client.hasRequestedStaff

appointment.remind

  • Approved appointment reminder is sent*
    ​(no related data)

*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

  • Appointment recall is sent*

recall.serviceId
recall.staffId
recall.message

*There are no events when the recall is created or cancelled.

appointment.online_payment

  • Online payment is made

appointment.payment.onlinePaymentAmount
appointment.payment.onlinePaymentStatus

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

  • New reservation is created by staff or client

booking.confirmationNumber

reservation.status_updated

  • Status is changed to Approved or Canceled (by staff, client or automatically*)

booking.status

​Group reservation is completed*

booking.isCompleted

*The rest of the reservation must not have changed.

reservation.updated

  • Price changed

booking.items[].price.amount
booking.items[].price.amountPerPerson
booking.items[].price.amountPerHour
booking.items[].price.visibility
booking.items[].price.tax
booking.items[].price.isStartingAt

reservation.updated

  • Communication preference changed

booking.clientCommunication

reservation.updated

  • Requester client changed

booking.client.id
booking.client.firstName
booking.client.lastName
booking.client.email
booking.client.homePhoneNumber
booking.client.mobilePhoneNumber
booking.client.remindByEmail
booking.client.remindBySMS
booking.client.additionalRequest
booking.client.isAttending
booking.client.hasRequestedStaff

reservation.updated

  • Attendee added, removed, reordered or changed

booking.attendees[].clientId
booking.attendees[].presence (arrived, no show..)
booking.attendees[].firstName
booking.attendees[].lastName
booking.attendees[].email
booking.attendees[].homePhoneNumber
booking.attendees[].mobilePhoneNumber
booking.attendees[].remindByEmail
booking.attendees[].remindBySMS
booking.attendees[].additionalRequest
booking.attendees[].surveyResponse

reservation.remind

  • Send approved reservation reminder **

(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

  • Sent when a payment is made

reservation.payment.onlinePaymentAmount
reservation.payment.onlinePaymentStatus

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

  • New Client Notification Sent or saved.

clientNotification.clientId
clientNotification.communicationMethod
clientNotification.email
clientNotification.mobilePhoneNumber
clientNotification.message

Client Consent Change

This event is triggered whenever a client grants or revokes a consent.

Webhook Event Type

Consent Operation

client.consent.changed

  • Consent is granted

  • Consent is revoked

Actions that trigger this event:

  • Appointment or reservation is booked

  • Appointment or reservation is created from the Back Office (implied mandatory consent)

  • Appointment client is changed from BNv3

  • Appointment client is changed from the Back Office (implied mandatory consent)

  • Cancelled appointment is reopened from the Back Office (implied mandatory consent, with the same consent version)

  • Booking is cancelled (except for cancellation through tracking page)

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.

  1. You can create a test account using

  2. Login to your account using:

    1. app.booxi.com (USA hosting)

    2. app.booxi.eu (EU hosting)

  3. 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:

  • None (i.e. the client gave their consent)

  • Staff (i.e. the booking was made by the staff on behalf of the client)

  • RequestingClient (i.e. the client reserved for a group and gave consent)

  • API (i.e. the consent was given through API)

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).

Did this answer your question?