All Projects

Calendly Booking Generator

Feb 10, 2025

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, so 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 enforcement. max_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
// NEXT STEP

Have a similar bottleneck?

Get a free automation audit. We'll map the system that takes the manual work off your team.