Widget

Paste one script tag where bookings should appear: <script src="https://daybooked.io/widget.js" data-org="your-slug" async></script>. 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).

Plain text

Install

Paste it where bookings should appear. The flow renders right after the tag, or inside data-target.

html
<script src="https://daybooked.io/widget.js" data-org="your-slug" async></script>

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
<script src="https://daybooked.io/widget.js" data-org="your-slug" data-mode="button" async></script>

Options

data-orgYour booking page slug. Required.
data-offeringOpen straight to one offering.
data-modeinline (default) renders in place; button floats a pill that opens it.
data-labelThe button's text. Default "Book now".
data-targetCSS selector to render into, instead of next to the tag.
data-themeThe 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-positionThe button's corner: right (default), or left to keep clear of a chat bubble.
data-offsetThe 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-analyticsYour 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:viewAn offering is shown or chosen: offering, with value at its price for one.
daybooked:beginThe guest reaches their details, once for each time and party they pick: value for them, quantity, starts_at.
daybooked:checkoutThe place is held and your page is about to go to Stripe Checkout: the reference too.
daybooked:bookedA booking confirms in the widget: reference, value, currency, offering, quantity, starts_at. One paid through Stripe Checkout doesn't fire it: see Paid bookings.
window event · daybooked:booked
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.

datalayerPushes GA4 ecommerce events to window.dataLayer for Google Tag Manager: view_item, begin_checkout at the details, purchase when booked.
gtagCalls 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.
fbqTracks the Meta Pixel's InitiateCheckout at the details and Purchase when booked, with value and currency, and the reference as Purchase's eventID.
html
<script src="https://daybooked.io/widget.js" data-org="your-slug" data-analytics="datalayer" async></script>

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.

node · your webhook endpoint
// 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)]);
}
node · GA4 Measurement Protocol
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,
          }],
        },
      }],
    }),
  });
}
node · Meta Conversions API
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.