For developers

Embedding the booking widget

One script, three display modes and a fallback for sites where a script cannot be added. The widget is under 4 kB gzipped and has no dependencies.

1. Basic embed

Put the script anywhere on the page, ideally with the defer attribute.

<script src="https://cdn.callania.eu/w.js" defer></script>
<div data-callania="your-slug" data-mode="inline"></div>

The script finds every element with a data-callania attribute and renders the widget into it. Elements added later (SPA, site editor) are picked up too.

2. Three display modes

ModeWhat it does
data-mode="inline"The widget is part of the page. Best for a dedicated “Booking” page.
data-mode="popup"Opens in a dialog on click. Attach your own button with data-trigger=".my-button"; without it we render ours.
data-mode="badge"A floating button in the corner of the screen, on every page.
<!-- your own button opens the dialog -->
<button class="my-button">Book now</button>
<div data-callania="your-slug" data-mode="popup" data-trigger=".my-button"></div>

<!-- floating button -->
<div data-callania="your-slug" data-mode="badge" data-label="Book"></div>

3. Deep links

The widget and the hosted page understand the same parameters. They work without JavaScript too — they are resolved on the server at render time.

ParameterMeaning
serviceslug of the service to preselect
staffslug of the staff member (“no preference” is the default)
datethe day the calendar should open on (2026-08-14)
checkin / checkoutstay range for accommodation
guestsnumber of guests
promodiscount code
langinterface language (sk, cs, en)
currencycurrency
refyour own source label — stored with the booking

Parameters can also be given as attributes on the element — those take precedence over the page address:

<div data-callania="your-slug"
     data-mode="inline"
     data-service="beard-trim"
     data-staff="george"
     data-lang="en"></div>

4. Events for your site

The widget sends the host page named events via postMessage. That is more reliable than injecting foreign code into the confirmation page.

window.addEventListener('message', (e) => {
  if (e.data?.source !== 'callania') return;

  switch (e.data.event) {
    case 'callania.service_selected':   // picked a service
    case 'callania.time_selected':      // picked a time
    case 'callania.booking_started':    // filling in details
    case 'callania.booking_completed':  // done → pixel
      dataLayer.push({ event: e.data.event, ...e.data.payload });
  }
});

If window.dataLayer exists on the page, the widget pushes the events into it itself. It also fires them as a CustomEvent on window.

EventPayload
callania.service_selected{ service, price }
callania.time_selected{ startAt, service }
callania.booking_started`{ service \room, nights }`
callania.booking_completed{ code, value, currency }

5. Fallback without a script

Wix, older WordPress or some CMSes will not let you add a script. A plain iframe works then — with loading="lazy" and a fixed height so there is no layout shift.

<iframe src="https://your-slug.callania.eu/?embed=1"
        width="100%" height="760" loading="lazy"
        title="Book an appointment" style="border:0"></iframe>

And for a QR code or a link in bio, a plain link is enough:

https://your-slug.callania.eu/?service=beard-trim&ref=instagram-bio

6. Widget height

The widget reports its height to the parent page as { source: "callania", event: "resize", payload: { height } }. The loader handles it for you, so you do not have to resize anything. With a plain iframe, set the height yourself or catch the message.

7. Visit source

The source is not carried in a cookie — inside an iframe it would be third-party and Safari and blockers would drop it. The loader therefore takes utm_*, gclid, fbclid, msclkid and ref from your page address and appends them to the widget address. The server stores them as first-party data for 30 days.

Partner links are attributed on a first click model, ads and campaigns on a last non-direct click model within a 30-day window. Your own ads therefore will not take a booking away from a partner.

8. All widget attributes

Only one is required — data-callania. Everything else has a sensible default.

AttributeMeaning
data-callaniathe business slug, e.g. salon-zatoka. Required.
data-modeinline (default), popup or badge.
data-serviceslug of the service to preselect. Without it the customer picks.
data-langsk, cs or en. Without it, the language of the business.
data-triggerCSS selector of your button for popup. Without it the widget renders its own.
data-labeltext on the floating button for badge.
data-domainthe custom domain of the business, if you have one set up. Without it the {slug}.callania.eu subdomain is used.

9. iCal feed for portals

Every accommodation unit has its own iCal availability feed. You will find the address in the admin under Portals and iCal; it contains a signed token, so it can be pasted straight into Booking.com or Airbnb.

https://your-slug.callania.eu/ical/<token>.ics

The feed is recalculated as soon as a booking changes, not when the portal asks — that lets it answer 304 Not Modified for unchanged content so the portal does not download needlessly. In the other direction we pull portal feeds every minute, conditionally via If-None-Match.

The token in the address is the only thing protecting the feed, so treat it like a password. If it leaks, it can be rotated in the admin — the portal then adds the feed again.

10. Consent for source tracking — data-consent

On your site you are the controller, not us. Consent for tracking the visit source is therefore yours to collect — with your cookie banner, in your words. We only ask for it.

<div data-callania="your-slug" data-consent="granted"></div>
ValueWhat it means
grantedthe visitor consented — we may store where they came from (30 days)
deniedthey did not consent — we measure nothing
attribute missingwe measure nothing. The default is no

Without consent the source is not transmitted at all — not only is it not stored, it is not even put into the address. Sending it and trusting the other side to drop it means having it in the access log.

The attribute can be changed at runtime. When a visitor changes their choice in your banner, rewrite data-consent and embed the widget again — reloading the page is simpler.

Booking works the same either way. Without consent you simply will not see where the customer came from; the appointment, the confirmation and the reminder do not change.

Our cookie banner appears neither on your site nor on your hosted page. It would speak for you and ask in the wrong name.

11. Content in shadow DOM

The widget runs in its own <callania-widget> with a shadow DOM. Your CSS cannot reach it and our CSS will not break your page — not even if you have global rules for iframe or button.