This post was written by Ryan Mercer, Salesforce Consultant at Kicksaw, and last updated on 3/23/21.

Here at Kicksaw, we pride ourselves on being experts at integrating tools into Salesforce. From Outreach to DocuSign to Conga, we’ve got tons of experience getting your other systems hooked into Salesforce so that you can not only hit the ground running, but be in line with best practices at the same time.

One of the tools we see our customers use regularly is Calendly, which offers super slick appointment scheduling software. Calendly plays quite well with the other tools in your tech stack through several integrations, including Salesforce.

The managed package that Calendly ships with its Salesforce integration is simple: one object CalendlyAction__c, one process builder OnCalendlyActionCreated, and two flows CreateEvent and CancelEvent. When someone books an appointment with you, Calendly creates one CalendlyAction__c record. When a CalendlyAction__c record is created, the process builder launches one of two flows: either the CreateEvent flow if it’s a new appointment or the CancelEvent flow if the appointment is canceled.

Much of the automation happens during the CreateEvent flow. Though their flow is a bit unwieldy, it is certainly functional.

Here’s the bird’s eye view of what happens during the flow:

  1. The flow first checks whether the appointment included an existing Salesforce record ID that may have been passed via URL parameter. (Check out these great instructions that explain how to do this.) It searches the following objects: account, opportunity, case, contract, campaign, contact, and lead.
  2. If the flow doesn’t find an existing Salesforce record ID, it tries to find an existing contact that matches the invitee’s email address. If it doesn’t find a contact, it then looks for an existing lead. (If it doesn’t find a lead, the flow will create one. More on that later.)
  3. Next, the flow tries to find the existing Salesforce user by the email address tied to this appointment. If it doesn’t find a user, it will grab a system administrator.
  4. Remember when I mentioned the flow will create a lead if it couldn’t find a matching contact or lead? It does this now.
  5. Finally, the flow creates an event, relating it to either the record ID from Step One, the contact / lead from Step Two, or the new lead from Step Four, and assigns it to either the user from Step Three or the system administrator.

In addition to Calendly mapping a bunch of standard, expected fields into Salesforce (such Event End Time and Event Start Time), it also maps four custom questions that you can specify in the Calendly UI.

A word of warning from Calendly themselves:

Be sure to ask identical questions in the same order for each event type to ensure the fields update correctly.

When Calendly automatically creates the CalendlyAction__c record (and launches the flow), it maps the four questions and responses to these fields.

Let’s say you have a custom field on the Event object Budget__c. When creating the Event record at the very end of the flow, you can assign the value of CustomResponse1 to Budget__c, and the newly created record will have $1,000,000 in the Budget field.

Now, let’s say you have a second event type (or a third or a fourth) that asks a different set questions. Sounds likely, right? You’re going to run into immediate issues creating your new Event records.

Take this event type as an example.

Pair it with the associated CalendlyAction__c record.

You can now see why Calendly notes to “be sure to ask identical questions in the same order for each event type to ensure the fields update correctly.” Your newly created Event record will have my LinkedIn URL (CustomResponse1) in the Budget field.

To solve this problem, please enjoy this nugget of Apex code.

//written by Ryan Mercer from Kicksaw, Feb 2021
public class calendly{
@InvocableMethod(
    label = 'Calendly Custom Field Mapping'
   )
   public static List<CalendlyAction__c> mapping(List<CalendlyAction__c> actions){
       CalendlyAction__c calendlyActionObjectName = actions[0];
       SObjectType type = calendlyActionObjectName.getSObjectType();
     Map<String,Schema.SObjectField> apiToApiMap = type.getDescribe().fields.getMap();
       Map<String,Schema.SObjectField> labelToApiMap = new Map<String,Schema.SObjectField>();
       CalendlyAction__c clonableAction = new CalendlyAction__c();
       for(String apiName : apiToApiMap.keySet()){
           SObjectField field = apiToApiMap.get(apiName);
           Schema.DisplayType fieldType = field.getDescribe().getType();
           if(fieldType==Schema.DisplayType.String||fieldType==Schema.DisplayType.TextArea){
          clonableAction.put(field,'');
           }
           labelToApiMap.put(field.getDescribe().getlabel(),field);
       }
       List<CalendlyAction__c> returnActions = new List<CalendlyAction__c>();
       for(CalendlyAction__c action : actions){
           CalendlyAction__c returnAction = clonableAction.clone();
           try{
               returnAction.put(labelToApiMap.get(action.CustomQuestion1__c),action.CustomResponse1__c);
           }catch(Exception e){
               System.debug('The following exception has occurred: ' + e.getMessage());
           }
           try{
               returnAction.put(labelToApiMap.get(action.CustomQuestion2__c),action.CustomResponse2__c);
           }catch(Exception e){
               System.debug('The following exception has occurred: ' + e.getMessage());
           }
           try{
               returnAction.put(labelToApiMap.get(action.CustomQuestion3__c),action.CustomResponse3__c);
           }catch(Exception e){
               System.debug('The following exception has occurred: ' + e.getMessage());
           }
           try{
               returnAction.put(labelToApiMap.get(action.CustomQuestion4__c),action.CustomResponse4__c);
           }catch(Exception e){
               System.debug('The following exception has occurred: ' + e.getMessage());
           }
        returnActions.add(returnAction);
       }
       return returnActions;
   }
}

Sure, Apex code is great, you say, but how do I use it?

First, create custom fields on the CalendlyAction__c object to EXACTLY MATCH the Calendly questions. Create one custom field for each unique question. For example, if your Calendly question is “LinkedIn URL”, that must be the exact label of the custom field you create.

Because there is a 40 character limit for Salesforce field labels, your Calendly questions will also be restricted to 40 characters. The custom fields MUST BE of types Text or Text Area.

Open the CreateEvent flow and create a new variable called calendlyOutput.

Add an action immediately after Start and before the first SFID Provided? decision.

Use the calendlyOutput variable to assign other fields. For example, calendlyOutput.LinkedIn_URL__c can be mapped to wherever you’d like to warehouse that data in Salesforce.

In my example, I created custom fields on the Event object to store each piece of data and added the mappings to the final Create Event element.

Now my Candidate Events in Salesforce look like this:

And my Sales Events look like this:

BOOM! 💥 All Calendly custom field data is where it’s supposed to be in Salesforce!

Want to take the Apex test class so that you can deploy to production? Connect with Ryan on LinkedIn and send me a message. Hope to hear from you!

Explore another topic:

Contact Us

Thank you! Your submission has been received!

Oops! Something went wrong while submitting the form