Embed Widget
Embedding the booking widget on your own website.
Overview#
The Bokko embed widget allows you to embed the entire booking process into any website — under your own domain, without visitors ever leaving.
- Two display modes: inline (embedded iframe) and popup (modal overlay).
- Single script tag:
https://booking.bokko.app/bokko-widget.js. - Automatic fallback: If the widget does not load, the visitor is directed straight to the booking page.
- postMessage communication: Be notified about the booking status through events.
Quickstart#
Inline Mode#
The simplest embedding — the widget appears directly on the page:
<div class="bokko-widget" data-mode="inline" data-slug="my-salon">
<a href="https://booking.bokko.app/my-salon">Open Booking</a>
</div>
<script src="https://booking.bokko.app/bokko-widget.js"></script>Popup Mode#
The booking process appears in a modal window that opens on a button click:
<div class="bokko-widget" data-mode="popup" data-slug="my-salon"
data-button-text="Book appointment!">
<a href="https://booking.bokko.app/my-salon">Book appointment!</a>
</div>
<script src="https://booking.bokko.app/bokko-widget.js"></script><a> tag placed inside the <div> serves a dual purpose: it acts as a fallback link if JavaScript does not load, and remains accessible to the booking page in noscript environments.Display Modes#
Inline#
An embedded iframe that appears directly on the page in place of the <div>.
- Fills the parent element at full width.
- Minimum height: 500px.
- Automatic height adjustment: The widget sends a
resizemessage when content height changes, and the iframe height adjusts accordingly. - Appears without borders, as a natural part of the page.
Popup#
A modal overlay that opens on button click.
- Maximum width: 580px, height: 90vh.
- Centered with a background overlay.
- Full-screen display for screen widths below 640px (mobile optimization).
- Closing options:
- Clicking the background (backdrop).
- Escape key.
- X button in the upper right corner.
Data Attributes#
You can configure the widget's behavior using data-* attributes on the <div> element:
| Attribute | Required | Values | Default | Description |
|---|---|---|---|---|
data-slug | Yes | string | — | The provider's URL slug |
data-mode | No | inline, popup | inline | Display mode |
data-button-text | No | string | Book appointment! | Popup button text (only in popup mode) |
The data-slug is the only mandatory attribute. It determines which provider's booking interface is displayed. The slug matches the identifier in the provider's public URL (e.g., booking.bokko.app/my-salon → data-slug="my-salon").
Events (postMessage)#
The widget communicates with the host page via the window.postMessage API. Every message includes the source, version, and type fields.
Available Events#
| Event | Payload | Description |
|---|---|---|
ready | — | The widget has loaded and can be displayed |
booking:confirmed | { slug: string } | The booking was successfully created |
booking:closed | — | The user closed the widget (popup mode) |
resize | { height: number } | The content height has changed (inline mode only) |
Message Format#
Every postMessage follows this structure:
{
"source": "bokko-widget",
"version": 1,
"type": "booking:confirmed",
"payload": { "slug": "my-salon" }
}Monitoring Events#
window.addEventListener('message', (event) => {
if (event.data?.source !== 'bokko-widget') return;
if (event.data.version !== 1) return;
switch (event.data.type) {
case 'ready':
console.log('Widget loaded');
break;
case 'booking:confirmed':
console.log('Successful booking:', event.data.payload.slug);
// e.g., send analytics event, show thank you page
break;
case 'booking:closed':
console.log('Widget closed');
break;
case 'resize':
console.log('New height:', event.data.payload.height);
break;
}
});event.data.source and event.data.version fields before processing the message. Messages can come from any source via the postMessage API — without source and version checks, you might accidentally process messages from other scripts.Fallback Behavior#
The widget has a built-in fallback mechanism in case the embedded iframe does not load:
- The script initializes the iframe.
- Wait for the
readyevent — for a maximum of 8 seconds. - If
readydoes not arrive in time, the widget reverts to the fallback link. - The fallback URL:
https://booking.bokko.app/{slug}.
<a> tag within the widget <div> with the appropriate booking URL. This ensures that the booking interface remains accessible in environments without JavaScript (noscript) and in case of loading errors.Security Model#
Origin Validation#
The host-side widget script only accepts messages coming from Bokko booking origins:
https://booking.bokko.apphttps://foglalas.bokko.app
Any postMessage arriving from another origin is silently discarded. This list is hardcoded in the widget JS — other origins (own staging, local dev iframe hosting) are not supported in v1.
Handshake Protocol#
The widget uses a simple handshake initiated by the iframe:
- The iframe sends a
readymessage after it has loaded. - The host validates the origin (see above), then sends a
handshakemessage back to the iframe with thetargetOrigin: https://booking.bokko.appparameter. - Further communication takes place over this verified channel.
Messages arriving before ready are ignored by the iframe; after ready, the host only processes those that contain the source: 'bokko-widget' and version: 1 fields.
PII Minimization#
postMessage messages only contain the provider's slug. The following are not transmitted via the widget:
- Guest personal data (name, email, phone number).
- Booking ID.
- Payment information.
Accessibility#
The widget provides the following accessibility features:
- Focus trap in popup mode: The Tab key navigates cyclically within the modal window, not exiting to the background.
- Escape key closes the popup.
- Backdrop click closes the popup.
- ARIA attributes: The close button has an
aria-labeltag. - The fallback
<a>link ensures basic accessibility for screen readers as well.
Testing#
Local Development#
The widget can be tested from a host page served from any origin (the widget does not check the host page origin) — however, the booking app serving the iframe is loaded under the booking.bokko.app domain via HTTPS. Locally:
- Load an HTML file in any way (
file://,localhost, custom domain) — thebokko-widget.jswill run. - The iframe and
postMessagecommunication only work if the iframe is served by the officialbooking.bokko.app(orfoglalas.bokko.app). - Customization for your own staging / your own bookingapp instance is not supported in v1 (the
ALLOWED_ORIGINSlist is hardcoded).
Debugging#
- Network tab (browser DevTools): Check if
bokko-widget.jsloads successfully (200 OK). - Console tab: You can monitor postMessage events using the listener described in the monitoring events section.
- Elements tab: Check if the iframe appears within the
.bokko-widgetdiv.
Checklist#
- [ ] The
data-slugattribute points to the correct provider. - [ ] The fallback
<a>link contains the correct URL. - [ ] In popup mode, the button appears and is clickable.
- [ ] The
readyevent arrives in the console. - [ ] In mobile view (below 640px), the popup is full-screen.
- [ ] The Escape key closes the popup.