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.
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.
Ten steps take a visitor from landing on a room page to a confirmed booking with a calendar event.
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.
If the room is sonic-studio, the heading and intro text are swapped to 'Book your Sonic Session today' with '(2 hour minimum)' blurb.
Inline flatpickr calendar (dynamically loaded). Weekends disabled. Dates must be 24 hours to 60 days in the future.
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.
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.
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.
User enters card or bank details in the Stripe Payment Element. Clicks 'Book Now'.
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.
booking.js calls stripe.confirmPayment() with the clientSecret. On success, user is redirected to /meeting-booking-confirmation.
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.
Users can also book multiple consecutive weekdays in a single transaction. The flow diverges after the calendar step:
GET /availability-range (single FreeBusy query spanning full range)Four services work together to power the booking system.
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.
Backend API — availability + payments + webhooks
Handles everything server-side: Google Calendar availability queries, Stripe customer/payment creation, webhook processing, and calendar event creation.
One-off payments
Processes one-off PaymentIntents (no subscriptions). Fires webhook after successful payment for calendar event creation.
Availability + event creation
Each room has its own Google Calendar resource calendar. Used for free/busy availability checks and event creation after payment.
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.
Sonic Studio has extra fields not used by other rooms.
Fields shown only for sonic-studio after time selection:
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.
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:
booking-embed.html (CSS, calendar HTML)booking-pay-embed.html (payment HTML)booking.txt (all logic, deploy worker)No Webflow publish needed for JS changes.
booking.txt + wrangler deploy (5 min cache)cd cloudflare-worker-bookings && wrangler deploynpx wrangler tail --format prettyGate 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 flagbooking_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.
booking.js cached 5 minutesRoom slug not in BOOKABLE array in booking.txt, or #booking-section missing from page
Check Google Calendar for existing events/blocks. Verify calendar_id in rooms.js matches the room's Google Calendar.
Check browser console for Stripe errors. Verify STRIPE_SECRET_KEY is set in worker.
Check Worker logs for [webhook] entries. Verify STRIPE_WEBHOOK_SECRET, GOOGLE_SERVICE_ACCOUNT_EMAIL, GOOGLE_PRIVATE_KEY secrets.
Gate 2 conflict detected (race condition). Check Worker logs. If legitimate, the slot was booked between selection and payment.
Origin not in ALLOWED_ORIGINS in worker.js. Add and redeploy.
Verify slug matches 'sonic-studio' exactly. Check #sonic-fields-wrapper exists in Webflow.
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.
Browser Console · Network tab · Cloudflare Workers Logs · Stripe Dashboard · Google Calendar (check events directly)
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).
| 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 |