Skip to content

Embed the booking widget

The widget shows your booking page on your site in a frame from book.tuppence.ai, as a pop-up button or inline steps. Clients stay on your site; bookings are marked source: widget.

Your pageTuppenceYour serverembed.js draws a frame from book.tuppence.aiclient books and paystuppence:booking.completedbooking.created webhook

Share → Your website generates the tag: choose A button where you put the code, A button in the corner of every page or The booking steps in the page itself, and optionally a starting service. It includes your accent colour and data-ch="website" for attribution.

<script async src="https://book.tuppence.ai/embed.js"
data-slug="bellas-barks" data-mode="button" data-ch="website"></script>
BuilderWhere the tag goes
WordPressA Custom HTML block; the footer for every page.
WixEvery page: Settings → Custom code → Add Custom Code, All pages, Body – end (needs Premium and a connected domain). On one page, Wix boxes pasted code and traps pop-ups, so use Add → Embed code → HTML iframe with data-mode="inline" and a tall box.
SquarespaceA Code block (Core plan and above); every page: Code Injection → Footer.
ShopifyOnline Store → Themes → Customize → Add section → Custom Liquid; the footer for every page.
Linktree, Instagram, other profilesUse your link, not the tag (see the share panel’s cards).
Attribute
data-slugRequired. Your address after book.tuppence.ai/.
data-modebutton (default: a button where the tag is), popup (a floating corner button) or inline (the flow in the page, full height, never scrolling itself).
data-service, data-variant, data-staff, data-location, data-datePreselect service (svc_…), option (svar_…), person (stf_…), place (loc_…) or day (YYYY-MM-DD).
data-chThe channel: website or a channel you’ve set up; unknown codes count as other.
data-label, data-colorButton text (default Book now) and colour (#RRGGBB); text is white, or near-black for contrast.
data-titleScreen-reader name for the pop-up and frame (default Book online).
data-testTest mode: no money moves.

As an element, after one embed.js tag without data-slug:

<script async src="https://book.tuppence.ai/embed.js"></script>
<tuppence-booking slug="bellas-barks" mode="inline"></tuppence-booking>

From your own button: TuppenceBook.open({ slug: "bellas-barks", service: "svc_…" }) and TuppenceBook.close().

Pop-upA dialog holding focus until closed (Close, Escape or click outside), then returning it to the button. Full-screen below 560 px.
InlineSized to the current step; your page scrolls, not the frame.
PayingDeposits and saved cards stay in the frame. Its allow="payment *" enables Apple Pay and Google Pay.

Your site never sees client input, and the frame needs no cookies. Two events bubble to window:

Eventdetail
tuppence:booking.completedbooking: id, status, starts_at, ends_at, next_step (no client details)
tuppence:booking.cancelledreason: "closed" (pop-up closed without booking)
window.addEventListener("tuppence:booking.completed", (event) => {
console.log("booked", event.detail.booking.id);
});

Browser events are lost if the page closes; rely on webhooks (booking.created, booking.confirmed).

embed.js is the current loader, cached five minutes. For Subresource Integrity, https://book.tuppence.ai/embed.json gives the current loader’s path (/embed.<version>.js: immutable, cached a year, served permanently) and integrity.

<script async src="https://book.tuppence.ai/embed.8bc7b2f42d.js"
integrity="sha384-xMs122Ue090L+BYAO2VZz1vS2VGFNmZ22EhrwRlXwWVtN8QdNRD2LV9bs3ji2Ix1"
crossorigin="anonymous" data-slug="bellas-barks" data-mode="button"></script>

Pinned loaders keep working but get no fixes; check embed.json periodically.

Allow https://book.tuppence.ai in script-src and frame-src; no inline script or style is needed.

Without scripts, put https://book.tuppence.ai/embed/<slug> in an <iframe> with a title, allow="payment *" and a fixed height. Only embed.js can resize it and pass on booking events.

Docs assistant

Open as a page
Loading the assistant…