Operations
Funnel Bot-Hardening — Close the Public Booking Door
Every funnel that ends in a calendar booking ships bot-hardened by default. Hidden event + no free-text notes field, zero friction for real leads. The rule that stops phishing bookings.
Funnel Bot-Hardening — Close the Public Booking Door
THE RULE: Every funnel that ends in a Cal.com (or Calendly/Acuity) booking ships bot-hardened at build time. The booking event is hidden from the public profile and its free-text notes field is removed — so spam bots can't find it and have nowhere to drop a payload. This is done with zero added friction for real leads (no email-verification, no requires-confirmation).
Why this exists (the 2026-06-23 incident)
MCM took a "booking" from Zoe Thistlethwaite that was a spam bot, not a lead. It booked directly on the public Cal page cal.com/mcm-private-care/phone-consultation, completely skipping the funnel (/start → /schedule → /api/qualify → /qualified). The booking's free-text notes field carried a phishing link (papershare.online/...docx) — a malware lure aimed at whoever opened the booking. The CEO tapped it on mobile (no harm — the page didn't load).
Root cause: the Cal event the embed books was fully public (hidden: false) and exposed an open free-text notes field. Bots scrape booking-tool profiles en masse and auto-submit; the notes box is the only thing they want — a place to deliver a link to a human.
The standard
A funnel's real qualification (who needs care · geo · hours · timeline) already lives in the funnel (/schedule → /api/qualify), gated before the calendar. So the booking event itself only needs to be invisible and payload-free:
- Hidden event. Set the booking event
hidden: true. This delists it from the public profile so enumeration bots can't find it. The inline embed still books it bycalLink— verified: a hidden event still returns slots and completes bookings via the embed path. Real leads coming through the funnel are unaffected. - No free-text notes. Hide the
notes/additionalNotesbooking field (hidden: true). That box is the bot's only payload slot; real funnel leads never use it because they already answered the qualifier. - Disable guests.
disableGuests: true— kills the multi-email guests vector. - No friction on real leads. Do not enable
requiresBookerEmailVerificationorrequiresConfirmation. Both add friction to genuine leads on a sales funnel. Hidden + no-notes stops the attack without gating real bookings. Instant-book stays ON. - Qualification belongs in the funnel, not the calendar. Never duplicate qualifying questions onto the Cal form — that both re-asks real leads and does nothing to the bots that bypass the funnel.
Build / API gotchas
- One PATCH, not many. Cal v2
PATCH /event-types/:idcalls clobber each other — sendingbookingFieldsresetsdisableGuests, and vice-versa. Sendhidden+disableGuests+bookingFieldsin a single PATCH. - Hidden ≠ 404. A hidden event's direct slug URL still returns HTTP 200, but it serves a "this event type is hidden / no longer available" page, not a booking form. For a fully-sealed door (rarely needed), switch the embed to a Cal private/hashed link — a code change in the embed + deploy.
- The webhook writes to CRM. The Cal booking webhook upserts each booking into
contacts(stage = consult_booked) and fires lead-engagement. An un-hardened funnel therefore pollutes the CRM and can auto-message bots — another reason hardening is mandatory, not optional.
Acceptance
- Every funnel's booking event is
hidden: truewith thenotesfield hidden and guests disabled, set in one PATCH. - No funnel enables email-verification or requires-confirmation as anti-bot (friction on real leads is banned).
- A test booking through the live embed path still succeeds (proves the funnel works), and the test booking is cancelled + leaves no CRM row.