# Widget Paste one script tag where bookings should appear: ``. Your booking flow renders in place, or behind a floating Book now button with `data-mode="button"`, and your page hears about each booking that confirms there through a `daybooked:booked` event (paid ones confirm after Stripe Checkout, on the `booking.confirmed` webhook). URL: https://daybooked.io/docs/widget ## Install Paste it where bookings should appear. The flow renders right after the tag, or inside `data-target`. ```html ``` ## Button A Book now pill in the bottom-right corner opens the flow in a dialog; Escape or a click outside closes it. On a dark site, `data-theme="light"` makes it white, or `"auto"` picks by what's behind it; `data-position="left"` and `data-offset` move it clear of a chat bubble or a cookie banner. ```html ``` ## Options - data-org (e.g. maple-street): Your booking page slug. Required. - data-offering (e.g. 8f1c2e4a-0b7d-4c55-9a7e-3f2d1c0b9a88): Open straight to one offering. - data-mode (e.g. button): inline (default) renders in place; button floats a pill that opens it. - data-label (e.g. Book a class): The button's text. Default "Book now". - data-target (e.g. #book): CSS selector to render into, instead of next to the tag. - data-theme (e.g. light): The button's look: dark (default), light (a white pill for dark sites), or auto, which picks by what's behind it as the page scrolls. - data-position (e.g. left): The button's corner: right (default), or left to keep clear of a chat bubble. - data-offset (e.g. 24 96): The button's distance from its corner in px: one number, or the side and the bottom (default 20). Lift it above a cookie banner. - data-analytics (e.g. datalayer fbq): Your page's tags that also get each event: datalayer, gtag, fbq (see Analytics). Off by default. ## Events Your page hears what a guest does as window events. Each `event.detail` has `org` and the booking's facts, never the guest's name, email or phone: `value` in major units (190 for $190), `currency` as an ISO code, `offering` as `{ id, name, kind }`, `starts_at` in UTC (`2026-12-05T15:00:00Z`). - daybooked:view: An offering is shown or chosen: `offering`, with `value` at its price for one. - daybooked:begin: The guest reaches their details, once for each time and party they pick: `value` for them, `quantity`, `starts_at`. - daybooked:checkout: The place is held and your page is about to go to Stripe Checkout: the `reference` too. - daybooked:booked: A booking confirms in the widget: `reference`, `value`, `currency`, `offering`, `quantity`, `starts_at`. One paid through Stripe Checkout doesn't fire it: see Paid bookings. ```js window.addEventListener("daybooked:booked", (event) => { const { reference, value, currency, offering } = event.detail; // e.g. "0GWYRZ2V", 190, "USD", { id, name, kind } }); ``` ## Analytics Tags on your page can't see inside the widget's frame, so the widget hands them each event, in their own names. Name your tags in `data-analytics`; it's off until you do. - datalayer: Pushes GA4 ecommerce events to `window.dataLayer` for Google Tag Manager: `view_item`, `begin_checkout` at the details, `purchase` when booked. - gtag: Calls `gtag("event", …)` with the same, on a site with the Google tag and no Tag Manager. Pick `datalayer` or `gtag`, not both, or GA4 hears each event twice. - fbq: Tracks the Meta Pixel's `InitiateCheckout` at the details and `Purchase` when booked, with `value` and `currency`, and the `reference` as Purchase's `eventID`. ```html ``` ### Google Tag Manager 1. Add `data-analytics="datalayer"` to the widget's tag. 2. In Tag Manager, add a tag of type Google Analytics: GA4 Event, with your measurement ID and `{{Event}}` as its Event Name. 3. Under More Settings → Ecommerce, tick Send Ecommerce data, with Data Layer as its source. 4. Fire it on a Custom Event trigger named `^(view_item|begin_checkout|purchase)$`, with Use regex matching. 5. Preview, make a test booking on your page (cancel it in daybooked after), then Submit. ### Meta Pixel With the Meta Pixel on your page, add `fbq`: `data-analytics="datalayer fbq"` feeds both. ### Paid bookings A paid booking confirms after Stripe Checkout, once the guest has left your page, so `daybooked:booked` never fires for it. Send it from your server when its `booking.confirmed` webhook arrives with a `payment_intent_id`; the bookings your page heard about have none, so nothing counts twice. ```js // Your endpoint for daybooked's webhooks, as Node's http.createServer(onWebhook). daybooked waits 5 seconds // for the answer, so check the signature (verify() from /docs/webhooks), answer, and only then send. async function onWebhook(req, res) { let body = ""; for await (const chunk of req) body += chunk; if (!verify(process.env.DAYBOOKED_WEBHOOK_SECRET, body, req.headers["daybooked-signature"] ?? "")) { return res.writeHead(401).end(); } res.writeHead(204).end(); const event = JSON.parse(body); // The sends you use. On a serverless host, hand them to its waitUntil rather than awaiting them here. await Promise.allSettled([sendToGA4(event), sendToMeta(event)]); } ``` ```js async function sendToGA4(event) { const booking = event.data; if (event.type !== "booking.confirmed" || !booking.payment_intent_id) return; const kinds = { class: "Class", appointment: "Private", rental: "Rental" }; const value = booking.total_cents / 100; // minor units to major: leave out "/ 100" for a zero-decimal currency such as JPY const url = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`; await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ // A stand-in, so GA4 records the sale without the visit's source: send the visitor's own client ID if your page saved it. client_id: booking.id, timestamp_micros: Date.parse(event.created_at) * 1000, events: [{ name: "purchase", params: { transaction_id: booking.reference, value, currency: "USD", // your org's currency items: [{ item_id: booking.offering.id, item_name: booking.offering.name, item_category: kinds[booking.offering.kind], quantity: booking.quantity, price: value / booking.quantity, }], }, }], }), }); } ``` ```js import { createHash } from "node:crypto"; async function sendToMeta(event) { const booking = event.data; if (event.type !== "booking.confirmed" || !booking.payment_intent_id) return; // Meta matches a sale to a person by their email: trimmed, lower case, SHA-256. const email = createHash("sha256").update(booking.customer.email.trim().toLowerCase()).digest("hex"); await fetch(`https://graph.facebook.com/v25.0/${PIXEL_ID}/events?access_token=${META_ACCESS_TOKEN}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ data: [{ event_name: "Purchase", event_time: Math.floor(Date.parse(event.created_at) / 1000), event_id: booking.reference, // the eventID of the Pixel's own Purchase, so Meta counts one action_source: "other", // "website" also needs the guest's browser user agent, which a webhook lacks user_data: { em: [email] }, custom_data: { value: booking.total_cents / 100, currency: "USD" }, // your org's currency; JPY and other zero-decimal ones: no "/ 100" }], }), }); } ``` ## Payments When guests pay at booking, the widget holds their place for 35 minutes and sends the page to Stripe Checkout. Paying confirms it, shows the guest a confirmation and emails them the link to their booking page. Follow those with `booking.confirmed` webhooks. ## Site builders Step-by-step guides for the website builders businesses use. - Squarespace (Code block): https://daybooked.io/guides/squarespace - Webflow (Code Embed): https://daybooked.io/guides/webflow - WordPress (Custom HTML block): https://daybooked.io/guides/wordpress - Wix (Embed HTML): https://daybooked.io/guides/wix - Shopify (Custom Liquid section): https://daybooked.io/guides/shopify - Framer (Embed): https://daybooked.io/guides/framer - Google Sites (Embed code): https://daybooked.io/guides/google-sites