All Projects

Calendly Booking Generator

Calendly Booking Link Generator

Sales and customer success teams generating outbound Calendly links were doing it manually: logging into Calendly, creating a link for each recipient, copying it into an email or CRM, and separately noting the activity somewhere. At any meaningful volume — SDR outreach sequences, onboarding invites, renewal touchpoints — that process added friction and left no audit trail.

We built an n8n webhook endpoint that generates a personalized, single-use Calendly scheduling link on demand, pre-fills it with the recipient's name, email, and UTM attribution, logs the link to Google Sheets, fires a Slack notification, and returns the complete URL in the HTTP response — all within a single API call.

The Workflow

Trigger: Webhook (HTTP POST)

Node 1 - Webhook Trigger Listens for POST requests at the path generate-calendly-link. Response mode is set to responseNode, which holds the HTTP connection open until the Respond to Webhook node fires at the end of the chain — the caller receives the generated link in the same request.


Stage 1: Input Normalization

Node 2 - Set Configuration Extracts four fields from the POST body and assigns fallback values for any absent parameters:

  • recipient_email — fallback: test@example.com
  • recipient_name — fallback: Test User
  • requested_event_type — the specific Calendly event type URI if provided; fallback: empty string (triggers auto-selection)
  • utm_source — fallback: n8n

Stage 2: Calendly User and Event Type Resolution

Node 3 - Get Current User (Calendly API) GET request to https://api.calendly.com/users/me using Calendly OAuth2 credentials. Returns the authenticated user's resource object including their URI, which is required to scope the subsequent event types query. Configured with onError: continueRegularOutput.

Node 4 - Extract User Reads the /users/me response and assigns two fields — user_uri and user_name — while preserving all prior fields for downstream use.

Node 5 - Get Event Types (Calendly API) GET request to https://api.calendly.com/event_types with two query parameters: user (the resolved user_uri) and active: true. Returns the list of active scheduling event types for the authenticated user. Configured with onError: continueRegularOutput.

Node 6 - Select Event Type Determines which event type to use for the link. If requested_event_type from the POST body is non-empty, that URI is used. Otherwise defaults to the first active event type returned by Calendly. Extracts selected_event_type_uri, selected_event_type_name, and selected_event_duration (fallback: 30 minutes).


Node 7 - Create Single-Use Link (Calendly API) POST request to https://api.calendly.com/scheduling_links with the following body:

{
  "max_event_count": 1,
  "owner": "{{ selected_event_type_uri }}",
  "owner_type": "EventType"
}

max_event_count: 1 enforces single-use behavior — the link expires after one booking. Configured with onError: continueRegularOutput.

Node 8 - Build Personalized Link Constructs the final URL by appending three URL-encoded query parameters to the base booking URL returned by Calendly: name, email, and utm_source. Pre-filling these parameters means the recipient lands on a form with their details already populated. Also captures link_created_at as an ISO timestamp. Seven fields are assembled here and carried forward to all three downstream nodes.


Stage 4: Logging, Notification, and Response

Node 9 - Log to Google Sheets Appends a row to the "Generated Links" tab with seven columns: Recipient Name, Recipient Email, Event Type, Duration (min), Booking URL, Created At, and Status (hardcoded as Sent). Configured with onError: continueRegularOutput — a Sheets failure does not block the Slack notification or the HTTP response.

Node 10 - Notify via Slack Posts a message to the #general channel with recipient name, email, event type name, duration, and the personalized booking URL as a hyperlinked "Click to Book" anchor. Markdown enabled, link unfurling disabled. Configured with onError: continueRegularOutput.

Node 11 - Respond to Webhook Returns HTTP 200 with a JSON body to the calling system:

{
  "success": true,
  "booking_url": "<personalized_booking_url>",
  "base_url": "<base_booking_url>",
  "recipient": { "name": "...", "email": "..." },
  "event": { "name": "...", "duration_minutes": 30 },
  "created_at": "<ISO timestamp>",
  "expires": "Single-use or 90 days"
}

The caller receives the fully formed link in the same HTTP response that triggered the workflow.


Results

  • One API call replaces the full manual link generation process — no Calendly login, no manual copy-paste, no separate logging step
  • Every generated link is personalized — recipient name, email, and UTM source are pre-filled as URL-encoded query parameters on the booking URL
  • Single-use enforcementmax_event_count: 1 ensures each link can only be used to book once
  • Full audit trail in Google Sheets — every link generated is logged with recipient details, event type, duration, timestamp, and status
  • Slack visibility — the team sees each generated link in real time without polling the sheet
  • Resilient by design — five nodes carry onError: continueRegularOutput so partial API failures in Calendly, Sheets, or Slack do not abort the response back to the caller

Stack

LayerTool
Automationn8n (self-hosted)
TriggerWebhook (HTTP POST, generate-calendly-link)
Scheduling APICalendly REST API v2 (OAuth2)
Endpoints UsedGET /users/me · GET /event_types · POST /scheduling_links
LoggingGoogle Sheets (append)
NotificationSlack
Responsen8n Respond to Webhook node

My Role

  • Mapped all four input fields with their fallback values and designed the input normalization stage to handle partial or absent POST body parameters gracefully
  • Wired the user_uri from /users/me directly into the user query parameter of the /event_types call
  • Implemented the event type selection logic: prefer the caller-specified URI, fall back to collection[0].uri, with a 30-minute duration fallback
  • Built the personalized URL by appending name, email, and utm_source as URL-encoded query parameters to the base booking URL
  • Configured the /scheduling_links POST body with max_event_count: 1 and owner_type: "EventType" to enforce single-use behavior
  • Defined all seven column mappings for the Google Sheets append, including the hardcoded Sent status value
  • Specified the Slack message structure with mrkdwn enabled, link unfurling disabled, and the booking URL rendered as a "Click to Book" anchor
  • Applied onError: continueRegularOutput to five nodes and configured responseMode: responseNode on the webhook trigger to hold the HTTP connection until the final response fires