Appearance
Advanced Usage
Analytics & GTM
The widget fires standard DOM CustomEvents with bubbles: true and composed: true. They cross the shadow DOM boundary and are visible at window level — no special configuration needed.
Event reference
| Event | Fires when | Detail payload |
|---|---|---|
booksea:widget-loaded | Widget data has loaded | { companySlug } |
booksea:availability-confirmed | Date and service are confirmed as available | { service, date, pax } |
booksea:checkout-started | Guest confirmed their details and the payment was prepared (clicked Next) | { pricingUuid, total, currency } |
booksea:payment-success | Stripe confirms the payment, or the booking needed no payment (e.g. a 100% promo code) | { bookingReference, total, currency } |
booksea:payment-failure | A payment attempt failed, or the payment was cancelled | { errorCode } — see error codes |
Listening to events
Subscribe to any event in plain JavaScript:
js
window.addEventListener('booksea:payment-success', function (e) {
console.log('Booking confirmed:', e.detail.bookingReference);
});Error codes
Possible values for errorCode in booksea:payment-failure:
| Code | Meaning |
|---|---|
payment_cancelled | Customer closed the payment window |
payment_declined | Card was declined or its details were rejected (e.g. expired, wrong CVC) |
payment_network_error | Network or connectivity failure during payment |
payment_timeout | Payment window stayed open for more than 5 minutes without completing |
payment_failed | Any other failure |
After a declined card or a network error, the payment window stays open so the guest can try again. The event fires once per failed attempt, so a single checkout can report several failures before a booksea:payment-success. Incomplete card fields are flagged by Stripe in the form and don't fire this event.
GTM bridge example
How you bridge events into dataLayer depends on your GTM setup. Here is one way to do it:
html
<script>
(function () {
window.dataLayer = window.dataLayer || [];
window.addEventListener('booksea:payment-success', function (e) {
var detail = e.detail || {};
window.dataLayer.push({
event: 'payment_success',
booksea_total: detail.total || '',
booksea_currency: detail.currency || 'EUR',
booksea_reference: detail.bookingReference || '',
});
});
window.addEventListener('booksea:checkout-started', function (e) {
var detail = e.detail || {};
window.dataLayer.push({
event: 'pay_now_click',
booksea_total: detail.total || '',
booksea_currency: detail.currency || 'EUR',
});
});
})();
</script>Umami bridge example
Umami records page views automatically, but not custom DOM events. Forward the widget's events with umami.track() by adding this to the page that hosts both the widget and your Umami script:
html
<script>
(function () {
[
'booksea:widget-loaded',
'booksea:availability-confirmed',
'booksea:checkout-started',
'booksea:payment-success',
'booksea:payment-failure',
].forEach(function (name) {
window.addEventListener(name, function (e) {
var data = Object.assign({}, e.detail);
delete data.bookingReference; // analytics doesn't need the booking id
if (window.umami) window.umami.track(name, data);
});
});
})();
</script>Each event then appears in Umami's Events view under its booksea: name, with the detail fields (total, currency, errorCode, …) as event properties.
Load the Umami script in the page <head> so it is ready before the widget fires booksea:widget-loaded. Events fired before Umami has loaded, or while it is blocked by an ad blocker, are skipped.