System Documentation

Meeting Room Booking System

Complete reference for the room booking flow — from date selection to calendar event creation. Covers real-time Google Calendar availability, Stripe payment processing, and automatic event creation with race-condition protection.

Stack Webflow · Cloudflare · Stripe · Google Calendar
Status Live
Site labourtemple.com

How it all connects

Webflow
Frontend — room pages + booking form
Cloudflare Worker
Backend API
Stripe
One-off PaymentIntents
Google Calendar
Availability + event creation

The booking system uses a custom-built Cloudflare Worker with Google Calendar for real-time availability and Stripe for payments. No third-party booking platform (like Calendly) needed.

From room page to confirmed booking

Ten steps take a visitor from landing on a room page to a confirmed booking with a calendar event.

Page load

booking.js runs on DOMContentLoaded, detects the room slug from the URL path. If the slug is in the BOOKABLE array, the booking section is revealed.

Booking JS

Sonic heading swap

If the room is sonic-studio, the heading and intro text are swapped to 'Book your Sonic Session today' with '(2 hour minimum)' blurb.

Booking JS

Date selection

Inline flatpickr calendar (dynamically loaded). Weekends disabled. Dates must be 24 hours to 60 days in the future.

Frontend

Availability check

When a date is selected, booking.js calls GET /availability with the room slug and date. Worker queries Google Calendar free/busy API and returns available time slots at 30-minute intervals.

Cloudflare Worker

Duration + time selection

User picks duration (1–9 hours based on room) and start time from the available slots. Selecting a time triggers progressive reveal of all remaining form sections.

Frontend

Form fields shown

Contact fields (name, email, phone, company, notes) are progressively revealed. For Sonic Studio, additional fields appear: podcast/show name, group size, set choice, special requests.

Webflow

Payment

User enters card or bank details in the Stripe Payment Element. Clicks 'Book Now'.

Stripe

Booking created

booking.js calls POST /create-booking with room, time, duration, and form data. Worker re-checks availability (Gate 1), creates Stripe Customer + PaymentIntent. Returns clientSecret.

Cloudflare Worker

Payment confirmed

booking.js calls stripe.confirmPayment() with the clientSecret. On success, user is redirected to /meeting-booking-confirmation.

Stripe

Webhook creates calendar event

Stripe fires payment_intent.succeeded to the Worker's /webhook endpoint. Worker re-checks availability (Gate 2 — race condition protection). If clear, creates Google Calendar event with booking details. If conflict, auto-refunds.

Cloudflare Worker Google Calendar

Multi-day booking variant

Users can also book multiple consecutive weekdays in a single transaction. The flow diverges after the calendar step:

  1. User checks "Book multiple consecutive days" — calendar switches to range mode
  2. User selects start and end dates — system calls GET /availability-range (single FreeBusy query spanning full range)
  3. All weekdays must be available. Weekends auto-skipped. Max 10 weekdays per booking.
  4. Default mode: Slot intersection computed — same duration and time every day
  5. Custom mode: Toggle "Customize duration and time for each day" — each day gets its own duration and time dropdowns, with independent pricing and discounts
  6. Single Stripe PaymentIntent for the total across all days
  7. Webhook creates one Google Calendar event per day. If any event fails, all created events are deleted and payment is auto-refunded.

What each service does

Four services work together to power the booking system.

Webflow

Frontend — room pages & booking form

Hosts room pages (Spaces CMS collection). Two embed blocks contain the booking calendar/dropdowns and payment element. Native form fields handle contact info.

Site: labourtemple.com
Embed 1: booking-embed.html (~7.8K chars) — calendar, dropdowns, CSS, booking.js
Embed 2: booking-pay-embed.html (~400 chars) — payment element, summary, disclaimer
Booking JS: served from Worker

Cloudflare Worker

Backend API — availability + payments + webhooks

Handles everything server-side: Google Calendar availability queries, Stripe customer/payment creation, webhook processing, and calendar event creation.

Name: labour-temple-bookings
URL: labour-temple-bookings.labourtemple.workers.dev
Endpoints: GET /booking.js, GET /availability, GET /availability-range, POST /create-booking, POST /webhook
Deploy: wrangler deploy

Stripe

One-off payments

Processes one-off PaymentIntents (no subscriptions). Fires webhook after successful payment for calendar event creation.

Mode: Live
Methods: Card + US Bank Account (ACH)
Type: One-off PaymentIntents
Webhook: payment_intent.succeeded → /webhook

Google Calendar

Availability + event creation

Each room has its own Google Calendar resource calendar. Used for free/busy availability checks and event creation after payment.

Auth: Service account (JWT, no Domain-Wide Delegation)
Free/busy: Availability queries + race condition gate
Event creation: On successful payment via webhook
Attendees: Not supported (service account limitation)

Available rooms & pricing

Seven rooms are bookable. Configuration lives in cloudflare-worker-bookings/src/rooms.js.

Room Capacity Hourly Rate Min Hours Volume Discount
Slinky 6 $50 1 hr 10% off 8+ hours
Day Office 6 $50 1 hr 10% off 8+ hours
Mackinaw 8 $75 1 hr 10% off 8+ hours
Shears 10 $75 1 hr 10% off 8+ hours
Board Room 20 $150 1 hr 10% off 8+ hours
Radiator Annex 80 $350 4 hrs
Sonic Studio $75 2 hrs

Operating hours are 8:00 AM – 5:00 PM Pacific, Monday–Friday. There's a 15-minute buffer between bookings. Minimum booking notice is 24 hours, and bookings can be made up to 60 days in advance.

To change room pricing or minimums, edit rooms.js and redeploy: cd cloudflare-worker-bookings && npx wrangler deploy

Multi-day booking limit: Users can book up to 10 consecutive weekdays in a single transaction. Weekends are automatically skipped. Controlled by constant MAX_MULTI_DAY = 10 in the Worker.

Additional booking fields for Sonic Studio

Sonic Studio has extra fields not used by other rooms.

Conditional Fields

Fields shown only for sonic-studio after time selection:

Storage & Integration

Values stored in Stripe PaymentIntent metadata with booking_sonic_ keys. Included in Google Calendar event description.

TODO: Heading/blurb currently hardcoded in booking.js — should be moved to CMS fields for easier updates.

Common tasks

Edit rooms.js, set name/capacity/calendar_id/hourly_rate/min_hours/discounts, deploy worker. Add slug to BOOKABLE array in booking.txt, deploy worker again.

Edit hourly_rate in rooms.js (in cents: $50 = 5000), deploy worker.

Three locations:

  • Embed 1: booking-embed.html (CSS, calendar HTML)
  • Embed 2: booking-pay-embed.html (payment HTML)
  • External JS: booking.txt (all logic, deploy worker)

No Webflow publish needed for JS changes.

  • Embeds: copy to Webflow + publish
  • External JS: edit booking.txt + wrangler deploy (5 min cache)
  • Worker: cd cloudflare-worker-bookings && wrangler deploy
  • Worker: npx wrangler tail --format pretty
  • Cloudflare Dashboard: Workers > Logs
  • Stripe Dashboard: Developers > Logs

Architecture decisions & patterns

Gate 1: Worker checks free/busy before creating PaymentIntent. Gate 2: Webhook re-checks after payment succeeds (race condition protection). If conflict at Gate 2, auto-refund.

Single-day: Stripe sends payment_intent.succeeded → Worker verifies signature → extracts booking metadata from PaymentIntent → Gate 2 check → if clear, create one Google Calendar event → if conflict or failure, auto-refund.

Multi-day: Same flow, but Gate 2 checks ALL days in the range. On success, creates one Google Calendar event per day with its own duration/time. If any event creation fails, Worker deletes all previously-created events (via deleteEvent from google-calendar.js) and refunds the full payment.

JWT-based auth, no Domain-Wide Delegation. Free/busy queries for availability. Event creation for confirmed bookings. Cannot add attendees to events.

Rooms can have discount tiers in rooms.js (e.g., {min_hours: 8, percent: 10}). Amount calculated as hours × rate × (1 - discount%). Applied automatically in /create-booking.

New endpoint: GET /availability-range — single FreeBusy query spanning entire date range, returns per-day availability slots. Max 10 weekdays per booking. Weekends auto-skipped.

Stripe metadata fields (multi-day):

  • booking_multi_day — boolean flag
  • booking_dates — comma-separated date range (YYYY-MM-DD)
  • booking_time — start time (default mode, same for all days)
  • booking_day_times — JSON array of per-day start times (custom mode)
  • booking_day_hours — JSON array of per-day durations in hours (custom mode)

10% full-day discount applied per-day independently.

  • No attendee invites on calendar events
  • No confirmation email (TODO)
  • Weekends disabled
  • Whole hours only
  • Operating hours 8 AM–5 PM Pacific hardcoded
  • Sonic heading hardcoded in JS
  • booking.js cached 5 minutes

Common issues & fixes

Booking section not showing

Room slug not in BOOKABLE array in booking.txt, or #booking-section missing from page

No available time slots

Check Google Calendar for existing events/blocks. Verify calendar_id in rooms.js matches the room's Google Calendar.

Payment fails after time selection

Check browser console for Stripe errors. Verify STRIPE_SECRET_KEY is set in worker.

Calendar event not created

Check Worker logs for [webhook] entries. Verify STRIPE_WEBHOOK_SECRET, GOOGLE_SERVICE_ACCOUNT_EMAIL, GOOGLE_PRIVATE_KEY secrets.

Auto-refund triggered

Gate 2 conflict detected (race condition). Check Worker logs. If legitimate, the slot was booked between selection and payment.

CORS error

Origin not in ALLOWED_ORIGINS in worker.js. Add and redeploy.

Sonic fields not showing

Verify slug matches 'sonic-studio' exactly. Check #sonic-fields-wrapper exists in Webflow.

Multi-day: partial event failure

If event creation fails mid-way through a multi-day booking, the Worker auto-deletes any already-created events and refunds the full payment. Check Worker logs for [webhook] cleanup entries.

Debugging tools

Browser Console · Network tab · Cloudflare Workers Logs · Stripe Dashboard · Google Calendar (check events directly)

All bookable spaces at a glance

Complete inventory of Labour Temple spaces — pricing, capacity, booking method, and content status. Use this to quickly check rates, verify CMS data, and identify missing content.

Multi-day bookings: All rooms support multi-day consecutive weekday bookings (max 10 weekdays per booking, weekends auto-skipped).

Data flags — needs attention

  • Shears: Capacity field = 6 but capacity-seated = 10, capacity-standing = 10. Likely a data entry error — the 6 should probably be removed or corrected to 10.
  • Radiator Annex: Primary descriptor says "Up to 100 people" but capacity field = 80 and seated capacity = 150. Three different numbers — needs reconciliation.
  • Board Room: CTA link points to OfficerRnD e-commerce (old system), not the new booking embed. May need to be updated or removed.
  • Day Office 116: Archived in CMS. "Day Office" (slug: day-office-115) is the live version. Confirm 116 should stay archived.
  • Sonic Studio pricing inconsistency: CMS price field = "$75/hr" but there are also separate "Hourly Sessions" and "4 Hour Session" CMS items in the Memberships collection — these appear to be misplaced.
  • Booking Options collection is empty. This collection exists (created 2026-03-13) but has no items. Unclear if it's in use or was deprecated in favor of rooms.js.
Space Capacity Rate Min Booking Method Slug Content Status
Meeting Rooms — Online Bookable · Mon–Fri 8am–5pm · 10% off 8+ hours
Slinky 6 / 6 $50/hr 1 hr Online slinky Short Long Image
Day Office
day-office-115
5 / 6 $50/hr 1 hr Online day-office All missing
Mackinaw 8 / 8 $75/hr 1 hr Online mackinaw Short Long Image
Shears
FLAG:Capacity mismatch
10 / 10
CMS says 6
$75/hr 1 hr Online shears Short Long Image
Board Room
FLAG:Old CTA link
20 / — $150/hr 1 hr Online board-room Short Long Image
Large Meeting & Workshop — Online Bookable
Radiator Annex
FLAG:Capacity inconsistency
80–100+
3 conflicting numbers
$350/hr 4 hrs Online radiator-annex Short Long Image
Event Spaces — Inquiry / Custom Quote
Card Room 35 / 50 $250/hr 4 hrs Inquire card-room Short Long Image
Member Lounge 50 / — From $1,200 Inquire member-lounge Short Long Image
Reading Room 70 / 100 $450/hr 6 hrs Inquire reading-room Short Long Image
Radiator Room
FLAG:No booking slug
150 / 250 $550/hr 6 hrs Inquire not set Short Long Image
Boiler Room
Coming Soon
30 / — $300/hr 4 hrs Inquire boiler-room Short Long Image
Sonic Studio — Podcast & Recording
Sonic Studio
FLAG:CMS items misplaced
3 on-camera $75/hr 2 hrs Online sonic-studio Short Image Long
Private & Dedicated Spaces — Monthly Lease / Membership
Founder Desk
1 Remaining
1 $500/mo Monthly Inquire All missing
Team Office Up to 24 From $1,500/mo Monthly Inquire Long Rest missing
Private Office Up to 24 From $1,600/mo Monthly Inquire All missing

Amenities by space category

Meeting Rooms

  • High Speed Wi-Fi
  • Video Conferencing
  • Video Screens
  • Easel Pads + White Boards
  • Catering Options
  • Inspiring Surroundings
  • Lounge Access
  • Lounge + Soft Seating
  • Bright Roomy Spaces

Event Spaces

  • Indoor Fireplace
  • Outdoor Fire Table
  • Chairs & Seated Rounds
  • In-House Event Concierge
  • Indoor/Outdoor Configurations
  • Mobile Bars
  • Cocktail Tables
  • Professional A/V Equipment
  • Video Screens
  • Catering Options
  • Glassware & Water Stations
  • Accessible Restrooms

Sonic Studio

  • Room For 3 Hosts/Guests
  • Choice of Three Sets
  • Premium Equipment
  • Coffee + Tea Service
  • Lounge Access
  • High Speed Wi-Fi

Private Offices / Members

  • Unlimited Access During Business Hours
  • 24/7/365 Building Access
  • Dedicated Workspace
  • Rooftop Terrace Access
  • Courtyard Access
  • Shower + Sauna
  • Fitness Room
  • Bike Lockers
  • Concierge Service
  • Espresso + Cocktail Bar
  • Coffee + Tea Service