Skip to main content

Calendly

Audience: Sales, management, support, engineering · Where in app: Settings → Integrations → Calendly · Plan availability: All plans (verify)

Calendly gives customers the familiar Calendly scheduling experience embedded directly inside the Knock Knock widget, while also capturing bookings made outside the widget (e.g. from an email link) via webhook. Every booking is treated as a direct-verification trigger and a hot-lead signal, so it lands on the visitor's timeline and bumps their lead score.

What it does

  • Embeds the Calendly inline scheduler inside the widget.
  • Captures bookings made anywhere (widget or external) via webhook.
  • Verifies the booker's identity and stitches the booking to the Person.
  • Fires the booked_meeting lead-score event and queues CRM sync.

How it works

Auth model: OAuth + webhook + embed. Connect from Settings → Integrations → Calendly. Stored on companies.calendly_integration:

  • access_token, refresh_token (auto-refreshed)
  • owner_uri (the connected user URI)
  • event_type_uri (selected event type, e.g. "30 min demo") + event_type_duration
  • scheduling_url, organization_uri, event_type_name
  • webhook_status (active | failed | null), webhook_error (≤300 chars), webhook_checked_at — written by calendlyApi.recordWebhookStatus() so the card can warn "Bookings are not syncing" with Retry

Calendly's OAuth, token storage, and webhook receipt/verification live in the backend, not ms-ai. The ms-ai Calendly module is a thin delegator: the AI agent's availability/booking calls go to backend GET /calendly/{company_id}/available-slots and POST /calendly/{company_id}/book-meeting (authenticated with the cross-service x-api-key).

Direction: inbound (bookings) + embed.

Widget embed

When connected, the AI agent can offer booking via a tool call ("Want to book a demo? Here's my calendar"), and the "Book a meeting" CTA opens the Calendly inline scheduler in the widget. The visitor picks a slot → booking created → Calendly sends the confirmation.

Webhook

Calendly's webhook fires on invitee.created and invitee.canceled (CALENDLY_WEBHOOK_EVENTS in app/lib/calendlyApi.js, which also owns token refresh + persistence on 401). A reschedule arrives as invitee.created carrying old_invitee; the paired invitee.canceled with rescheduled: true is ignored. Reschedules and cancels now emit the tenant booking_rescheduled / booking_cancelled webhook events. On receipt, the backend:

  1. Unifies the booking onto PersonProfile.bookings[] with source=calendly.
  2. Creates a BookingModel row.
  3. Fires the booked_meeting lead-score event.
  4. Queues CRM sync.

This means bookings made via Calendly outside the widget (e.g. from an email link) still appear on the Person's timeline.

Identity verification

When the form intercept detects calendly.event_scheduled, the email is captured directly from the Calendly invitee data, the session is verified (direct trigger), and the Person is stitched to the booking.

Multiple event types

An auxiliary CalendlyModel tracks per-event-type configuration when more than one event type is needed.

Configuration & options

ItemDetail
ConnectSettings → Integrations → Calendly (OAuth)
Event typeOne default event_type_uri per company
Backend availabilityGET /calendly/{company_id}/available-slots
Backend bookingPOST /calendly/{company_id}/book-meeting
Webhook eventsinvitee.created, invitee.canceled

Slot-already-taken: the booking flow handles the already_filled error with a "slot taken" message to the visitor.

Behaviors & edge cases

  • One event type per company: to direct visitors to different scheduling links per context, use the widget's page rules + the AI agent system prompt.
  • Cancellations / reschedules: mirrored onto the booking copies and dispatched as webhook events; there's no auto-rebook flow.
  • Re-subscribe sweep (app/lib/calendlyWebhookResubscribe.js, admin page Integrations Maintenance → POST /admin/maintenance/calendly-resubscribe + jobs poll, background-only): matches subscriptions by callback path /calendly/webhook; on apply it deletes all of ours first, then creates — Calendly allows one subscription per callback URL — and restores the previous event set if the create fails. Seven free-plan accounts can't hold webhooks at all.
  • Backfill: the webhook only fires for new bookings — bookings from old emails require a manual import (admin tool).
  • Free-tier webhook limits: Calendly's free tier limits webhooks; make sure the workspace can receive them.
  • Time zones: come from Calendly; the widget displays the visitor's local time.

Plan & limits

  • Available on all plans (verify exact tier gating).
  • Webhook availability depends on the customer's Calendly plan.

Technical implementation

  • Owning service: backend holds OAuth, token storage, the webhook endpoint, and booking persistence (BookingModel, CalendlyModel).
  • AI-side delegation: ms-ai src/integrations/actions/calendly/calendly.service.ts → backend availability/booking endpoints, via the unified calendar-router.service.ts.
  • Config: companies.calendly_integration.*.

What Nox can tell you

  • Connection status.
  • Recent Calendly bookings (knox-recent-bookings(source=calendly)).
  • Booking attribution to the visitor journey; "of yesterday's bookings, how many came via Calendly?" (split by source).

Tools: knox-recent-bookings, knox-visitor-bookings.