Connect Calendly with Salesforce

Implementation Guide

Overview: Connecting Calendly and Salesforce

Calendly is the scheduling layer that sits at one of the most critical conversion points in a B2B sales cycle: the moment a prospect agrees to meet with a sales representative. Every meeting booked through Calendly—whether a discovery call, a demo, a technical evaluation session, or a renewal check-in—represents a qualified intent signal that should immediately trigger downstream workflows in Salesforce. Without an integration, the Calendly booking exists in isolation: the sales rep receives an email notification, the prospect gets a calendar invite, and nothing automatically happens in the CRM. The Lead remains in its pre-meeting status, no Activity is logged, no follow-up task is queued, and the Opportunity pipeline does not reflect the advancing engagement.

The Calendly-to-Salesforce integration automates this entire downstream chain. A booking event from Calendly triggers Lead creation or update in Salesforce, logs the meeting as a Salesforce Event or Activity, advances the Lead or Contact's status to reflect the meeting has been scheduled, and optionally creates a follow-up Task reminding the rep to update the meeting outcome after the call. The net effect is that Salesforce becomes an accurate reflection of sales engagement activity in real time, enabling better pipeline forecasting, more accurate rep activity reporting, and faster response times on inbound meeting requests from high-value prospects.

Core Prerequisites

On the Calendly side, webhook subscriptions are managed via the Calendly API v2 rather than through the web UI. You must have a Calendly account at the Teams tier or higher to access webhook functionality—webhooks are not available on Free or Essentials plans. Authenticate against the Calendly API using a Personal Access Token generated under Integrations > API & Webhooks in your Calendly account settings, or using OAuth 2.0 if you are building a multi-user integration. For OAuth 2.0, register your application via Calendly's developer portal and use the standard authorization code flow; the access token must be passed as a Bearer token in the Authorization header. To create a webhook subscription programmatically, POST to https://api.calendly.com/webhook_subscriptions with a payload specifying the organization or user scope, the listener URL, and the events array. The events you must subscribe to are invitee.created (fired when any booking is made), invitee.canceled (fired on cancellation), and routing_form_submission.created if you use Calendly's Routing Forms to qualify prospects before scheduling.

It is important to note that Calendly webhook payloads include the invitee's name, email, and the answers to any Calendly intake form questions they completed during booking. This intake form data is extremely valuable for Salesforce Lead enrichment—company name, job title, use case, team size, and budget qualification answers can be mapped directly to Salesforce Lead fields if your Calendly event types are configured to collect them. Configure your Calendly event types with intake questions for Company, Job Title, Phone Number, and any qualification questions relevant to your sales process before connecting the integration, so that the initial meeting booking carries the maximum amount of enrichment data into Salesforce.

On the Salesforce side, you require a Connected App with api, refresh_token, and offline_access OAuth 2.0 scopes. The integration service account must have Create and Edit permissions on the Lead, Contact, Task, and Event objects. If your integration creates Salesforce Events (calendar activities) rather than Tasks, ensure the service account has "Edit Events" permission. For orgs using Salesforce's Einstein Activity Capture, verify that activities created via API are not filtered out of the Activity Timeline by your org's Einstein Activity Capture sync settings, as API-created activities can sometimes be excluded from the timeline view depending on configuration.

Top Enterprise Use Cases

The primary use case is automated Lead creation and enrichment at the moment of meeting booking. When a prospect books a discovery call via a Calendly link shared in a sales outreach email, Calendly fires invitee.created. The integration creates a Salesforce Lead pre-populated with the invitee's name, email, company (from intake form), and job title, sets the LeadSource to "Inbound - Meeting Booked," and sets Status to "Scheduled." For prospects who already exist as Leads or Contacts in Salesforce (matched on email), the integration updates the existing record's status and logs the upcoming meeting, avoiding duplicate record creation.

Meeting activity logging is the second essential use case. Rather than relying on sales reps to manually log scheduled calls in Salesforce, the integration automatically creates a Salesforce Event on the Lead or Contact record with the meeting subject (Calendly event type name), start datetime, end datetime, and meeting location or video conferencing link from the Calendly payload. This gives sales managers accurate activity reporting and pipeline coverage analysis without requiring rep data entry discipline.

Cancellation and rescheduling handling is a third use case that is frequently overlooked in naive implementations. When Calendly fires invitee.canceled, the integration should update the associated Salesforce Lead or Contact status back to its pre-meeting state, update or cancel the Salesforce Event record corresponding to the meeting, and create a follow-up Task assigned to the account owner prompting re-engagement outreach. Without this cancellation handling, Salesforce will show meetings as "scheduled" that were never held, corrupting activity metrics and pipeline stage accuracy.

A fourth high-value use case is Routing Form-to-Lead qualification. Calendly's Routing Forms allow prospects to answer qualification questions before being presented with a booking link for the appropriate meeting type. The routing_form_submission.created event delivers the form answers before any booking occurs, enabling the integration to create a Salesforce Lead at the routing stage and qualify it with the form data even if the prospect does not proceed to book a meeting, capturing top-of-funnel intent signals that would otherwise be invisible to the CRM.

Step-by-Step Implementation Guide

When Calendly delivers an invitee.created webhook event to your listener endpoint, the payload structure follows Calendly API v2's envelope format:

{
  "event": "invitee.created",
  "payload": {
    "event_type": {
      "name": "30-Minute Discovery Call",
      "uri": "https://api.calendly.com/event_types/AAAA0000000001"
    },
    "event": {
      "uri": "https://api.calendly.com/scheduled_events/BBBB0000000001",
      "start_time": "2025-09-20T14:00:00.000Z",
      "end_time": "2025-09-20T14:30:00.000Z",
      "location": {
        "type": "zoom",
        "join_url": "https://zoom.us/j/0000000000"
      }
    },
    "invitee": {
      "uri": "https://api.calendly.com/scheduled_events/BBBB0000000001/invitees/CCCC0000000001",
      "name": "Jordan Smith",
      "email": "[email protected]",
      "questions_and_answers": [
        { "question": "Company Name", "answer": "Prospect Corp" },
        { "question": "Job Title", "answer": "Head of Engineering" },
        { "question": "Team Size", "answer": "50-200" }
      ],
      "cancel_url": "https://calendly.com/cancellations/CCCC0000000001",
      "reschedule_url": "https://calendly.com/rescheduling/CCCC0000000001"
    }
  }
}

Your middleware first extracts payload.invitee.email and queries Salesforce to determine whether this prospect already exists: SELECT Id, Type FROM Lead WHERE Email = '[email protected]' AND IsConverted = false LIMIT 1. Separately, query Contacts: SELECT Id, AccountId FROM Contact WHERE Email = '[email protected]' LIMIT 1. This two-query pattern is necessary because the email may exist as either a Lead or a Contact depending on where the prospect is in your funnel.

If no existing record is found, create a new Salesforce Lead:

{
  "FirstName": "Jordan",
  "LastName": "Smith",
  "Email": "[email protected]",
  "Company": "Prospect Corp",
  "Title": "Head of Engineering",
  "LeadSource": "Inbound - Meeting Booked",
  "Status": "Meeting Scheduled",
  "Calendly_Event_URI__c": "https://api.calendly.com/scheduled_events/BBBB0000000001",
  "Calendly_Meeting_Type__c": "30-Minute Discovery Call",
  "Lead_Qualifier_Team_Size__c": "50-200"
}

After creating or identifying the Lead or Contact, create the Salesforce Event linked to the record. The WhoId field on a Salesforce Event accepts either a Lead ID or a Contact ID. POST to /services/data/v58.0/sobjects/Event/:

{
  "WhoId": "00QHs00000XxYyyIAE",
  "Subject": "Discovery Call - Jordan Smith (Prospect Corp)",
  "StartDateTime": "2025-09-20T14:00:00.000Z",
  "EndDateTime": "2025-09-20T14:30:00.000Z",
  "Description": "Zoom: https://zoom.us/j/0000000000\n\nCalendly Event: https://api.calendly.com/scheduled_events/BBBB0000000001",
  "Location": "Zoom Video Conference",
  "ShowAs": "Busy"
}

For Zapier, use the Calendly "Invitee Created" trigger, which provides the full invitee and event payload as mapped fields. A Salesforce "Find Lead" step checks for existing records by email. A Paths step branches on whether the Find step returned a result, routing to either "Update Lead in Salesforce" or "Create Lead in Salesforce." A second Salesforce "Create Event" action step runs unconditionally on both paths, using the resolved Lead ID from either the Find step or the Create step as the WhoId value. Map the Calendly start_time and end_time fields to the Salesforce Event's StartDateTime and EndDateTime—note that Calendly delivers times in ISO 8601 UTC format, which Salesforce accepts directly without conversion.

For Make, build two parallel scenarios. The first handles invitee.created using a Calendly "Watch Events" module (configured as a custom webhook receiver for the Calendly webhook subscription). Route the payload through a Salesforce "Search Records" module querying Leads by email, then a Router for the create/update branch, followed by a Salesforce "Create a Record" module for the Event. The second scenario handles invitee.canceled by querying Salesforce for Events where Description contains the Calendly event URI (stored during creation), updating the Event's status, and creating a re-engagement Task.

Common Pitfalls & Troubleshooting

Calendly's API v2 webhooks deliver payloads containing URIs rather than raw IDs for most resources. The invitee.uri, event.uri, and event_type.uri fields are full API endpoint URLs—for example, https://api.calendly.com/scheduled_events/BBBB0000000001—rather than bare UUID strings. If your integration needs to make secondary API calls to Calendly to fetch additional event or invitee details, you can use these URI values directly as the request URL rather than constructing them from extracted IDs. If you only need the resource ID itself (for storage in Salesforce), extract it by splitting the URI on the final / character. Hard-coding assumptions about URI format is fragile; extract programmatically.

A INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY error from Salesforce when creating an Event with a WhoId referencing a Lead typically indicates that the integration service account does not own the Lead record and your Salesforce org's sharing rules prevent API users from creating activities on records they do not own. Resolve this by either granting the service account a permission set that includes "Modify All Data" on the Activity object, or by configuring Salesforce's Organization-Wide Default for Activities to "Controlled by Parent"—which inherits the sharing model of the associated Lead or Contact—and ensuring the service account has Read access to the parent Lead via a sharing rule or territory.

A subtle data quality issue arises when the same prospect books multiple meetings over time—for example, booking a discovery call, canceling it, rescheduling, and then later booking a demo. Each booking fires a separate invitee.created event, and a naive integration creates a new Salesforce Lead on each event rather than linking all events to the same Lead record. The email-based deduplication check described in the implementation guide prevents duplicate Lead creation, but your integration must also handle the case where the existing Lead has been converted to a Contact between bookings—meaning the email exists in Salesforce as a Contact, not a Lead—and route accordingly.

Calendly's intake form question labels are free-text strings set by the event type creator, meaning they can change if your marketing or sales team renames a question. If you are mapping specific question answers to Salesforce fields by matching the question string (e.g., matching "Company Name" to the Company Lead field), a question rename silently breaks the mapping without any error. Implement mapping by question position index rather than question string, or maintain a configuration table that maps expected question strings to Salesforce fields and emit an alert when a received question string does not match any configured mapping key.