Different Hunger

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:

  1. 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 by calLink — verified: a hidden event still returns slots and completes bookings via the embed path. Real leads coming through the funnel are unaffected.
  2. No free-text notes. Hide the notes / additionalNotes booking field (hidden: true). That box is the bot's only payload slot; real funnel leads never use it because they already answered the qualifier.
  3. Disable guests. disableGuests: true — kills the multi-email guests vector.
  4. No friction on real leads. Do not enable requiresBookerEmailVerification or requiresConfirmation. Both add friction to genuine leads on a sales funnel. Hidden + no-notes stops the attack without gating real bookings. Instant-book stays ON.
  5. 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/:id calls clobber each other — sending bookingFields resets disableGuests, and vice-versa. Send hidden + disableGuests + bookingFields in 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: true with the notes field 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.