Popular Integrations
Calendly

Calendly Integration

Send automated SMS reminders and confirmations for scheduled meetings through Calendly. Keep your attendees informed with timely notifications before, during, and after their appointments.

Use Case: Reduce no-shows and improve attendance with automated SMS notifications for Calendly bookings.

How It Works

Calendly webhooks trigger SMS notifications when:

  • New events are scheduled
  • Events are cancelled
  • Events are rescheduled
  • Invitee responses are received
  • Reminders are due

Prerequisites

Before you begin, ensure you have:

  • An active SMSLeopard account with API credentials (Get started here)
  • Calendly account (Professional plan or higher for webhook access)
  • A webhook endpoint to receive Calendly notifications

Integration Steps

Step 1: Create Webhook Endpoint

Set up an endpoint to receive webhooks from Calendly:

const express = require('express');
const axios = require('axios');
const crypto = require('crypto');
const app = express();
 
app.use(express.json());
 
// Calendly webhook endpoint
app.post('/calendly/webhook', async (req, res) => {
try {
// Verify webhook signature
if (!verifyCalendlySignature(req)) {
return res.status(401).json({ error: 'Invalid signature' });
}
 
    const { event, payload } = req.body;
 
    console.log('Calendly webhook received:', event);
 
    // Handle different event types
    switch (event) {
      case 'invitee.created':
        await handleEventScheduled(payload);
        break;
      case 'invitee.canceled':
        await handleEventCanceled(payload);
        break;
      case 'invitee.rescheduled':
        await handleEventRescheduled(payload);
        break;
      default:
        console.log('Unhandled event type:', event);
    }
 
    res.json({ success: true });
 
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({ error: error.message });
}
});
 
// Verify Calendly webhook signature
function verifyCalendlySignature(req) {
const signature = req.headers['calendly-webhook-signature'];
const timestamp = req.headers['calendly-webhook-timestamp'];
 
if (!signature || !timestamp) {
return false;
}
 
const webhookSigningKey = process.env.CALENDLY_WEBHOOK_SIGNING_KEY;
const data = timestamp + '.' + JSON.stringify(req.body);
 
const expectedSignature = crypto
.createHmac('sha256', webhookSigningKey)
.update(data)
.digest('hex');
 
return signature === expectedSignature;
}
 
// Handle event scheduled
async function handleEventScheduled(payload) {
const invitee = payload.invitee;
const event = payload.event;
 
const phoneNumber = extractPhoneNumber(invitee.questions_and_answers);
 
if (!phoneNumber) {
console.log('No phone number found for invitee');
return;
}
 
const eventTime = new Date(event.start_time);
const formattedTime = eventTime.toLocaleString('en-US', {
dateStyle: 'full',
timeStyle: 'short'
});
 
const message = `Hi ${invitee.name}! Your appointment "${event.name}" is confirmed for ${formattedTime}. We look forward to meeting you!`;
 
await sendSMS(phoneNumber, message);
 
// Schedule reminder for 24 hours before
scheduleReminder(phoneNumber, invitee.name, event, eventTime);
}
 
// Handle event canceled
async function handleEventCanceled(payload) {
const invitee = payload.invitee;
const event = payload.event;
 
const phoneNumber = extractPhoneNumber(invitee.questions_and_answers);
 
if (!phoneNumber) {
return;
}
 
const message = `Your appointment "${event.name}" has been canceled. If you'd like to reschedule, please visit our booking page.`;
 
await sendSMS(phoneNumber, message);
}
 
// Handle event rescheduled
async function handleEventRescheduled(payload) {
const invitee = payload.invitee;
const event = payload.event;
const oldEvent = payload.old_event;
 
const phoneNumber = extractPhoneNumber(invitee.questions_and_answers);
 
if (!phoneNumber) {
return;
}
 
const newTime = new Date(event.start_time);
const formattedTime = newTime.toLocaleString('en-US', {
dateStyle: 'full',
timeStyle: 'short'
});
 
const message = `Your appointment has been rescheduled to ${formattedTime}. See you then!`;
 
await sendSMS(phoneNumber, message);
}
 
// Extract phone number from custom questions
function extractPhoneNumber(questionsAndAnswers) {
if (!questionsAndAnswers) return null;
 
const phoneQuestion = questionsAndAnswers.find(
qa => qa.question.toLowerCase().includes('phone')
);
 
return phoneQuestion ? phoneQuestion.answer : null;
}
 
// Schedule reminder
function scheduleReminder(phoneNumber, name, event, eventTime) {
const reminderTime = new Date(eventTime.getTime() - 24 _ 60 _ 60 \* 1000);
const delay = reminderTime.getTime() - Date.now();
 
if (delay > 0) {
setTimeout(async () => {
const formattedTime = eventTime.toLocaleString('en-US', {
timeStyle: 'short'
});
 
      const message = `Reminder: Hi ${name}, your appointment "${event.name}" is tomorrow at ${formattedTime}. Looking forward to it!`;
 
      await sendSMS(phoneNumber, message);
    }, delay);
 
}
}
 
// Send SMS using SMSLeopard API
async function sendSMS(phoneNumber, message) {
const apiKey = process.env.SMSLEOPARD_API_KEY;
const apiSecret = process.env.SMSLEOPARD_API_SECRET;
const sourceAddress = process.env.SENDER_ID;
 
const formattedPhone = formatPhoneNumber(phoneNumber);
 
try {
const response = await axios.post(
'https://api.smsleopard.com/v1/sms/send',
{
source: sourceAddress,
destination: [formattedPhone],
message: message
},
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}:${apiSecret}`
}
}
);
 
    console.log('SMS sent successfully:', response.data);
    return response.data;
 
} catch (error) {
console.error('Error sending SMS:', error.response?.data || error.message);
throw error;
}
}
 
function formatPhoneNumber(phone) {
phone = phone.replace(/[\s\-\(\)]/g, '');
 
if (phone.startsWith('0')) {
return '254' + phone.substring(1);
} else if (phone.startsWith('+')) {
return phone.substring(1);
}
 
return phone;
}
 
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Calendly webhook server running on port ${PORT}`);
});
 

Step 2: Register Webhook in Calendly

  1. Log in to Calendly and navigate to Integrations > API & Webhooks
  2. Click Create Webhook Subscription
  3. Enter your webhook URL (e.g., https://yourdomain.com/calendly/webhook)
  4. Select the events you want to receive:
    • invitee.created
    • invitee.canceled
    • invitee.rescheduled
  5. Copy the Webhook Signing Key and add it to your environment variables

Step 3: Add Phone Number Field

In your Calendly event settings:

  1. Go to Event Types and select your event
  2. Add a custom question: "Phone Number"
  3. Make it required for booking
  4. Set input type to "Phone number"

Message Templates

Create effective SMS templates for different scenarios:

const messageTemplates = {
  confirmation: (name, eventName, time) =>
    `Hi ${name}! Your ${eventName} is confirmed for ${time}. See you then!`,
 
  reminder24h: (name, eventName, time) =>
    `Reminder: Hi ${name}, your ${eventName} is tomorrow at ${time}. Looking forward to it!`,
 
  reminder1h: (name, eventName) =>
    `Hi ${name}, your ${eventName} starts in 1 hour. We're ready for you!`,
 
  cancellation: (name, eventName) =>
    `Hi ${name}, your ${eventName} has been canceled. Book again anytime at [link]`,
 
  reschedule: (name, newTime) =>
    `Hi ${name}, your appointment has been moved to ${newTime}. Thanks for your flexibility!`,
 
  thankYou: (name) =>
    `Thank you ${name} for meeting with us! We've sent a follow-up email with next steps.`
};

Advanced Features

Multiple Reminders

Send reminders at different intervals:

function scheduleMultipleReminders(phoneNumber, name, event, eventTime) {
  const reminders = [
    { hours: 24, message: 'tomorrow' },
    { hours: 2, message: 'in 2 hours' },
    { hours: 0.25, message: 'in 15 minutes' }
  ];
  
  reminders.forEach(reminder => {
    const reminderTime = new Date(
      eventTime.getTime() - reminder.hours * 60 * 60 * 1000
    );
    const delay = reminderTime.getTime() - Date.now();
    
    if (delay > 0) {
      setTimeout(async () => {
        const message = `Reminder: Hi ${name}, your appointment "${event.name}" is ${reminder.message}!`;
        await sendSMS(phoneNumber, message);
      }, delay);
    }
  });
}

Meeting Join Links

Include meeting links in SMS:

async function handleEventScheduled(payload) {
  const invitee = payload.invitee;
  const event = payload.event;
  const joinUrl = event.location?.join_url;
 
  const phoneNumber = extractPhoneNumber(invitee.questions_and_answers);
 
  if (!phoneNumber) return;
 
  let message = `Hi ${invitee.name}! Your appointment is confirmed for ${formattedTime}.`;
 
  if (joinUrl) {
    message += ` Join here: ${joinUrl}`;
  }
 
  await sendSMS(phoneNumber, message);
}

No-Show Follow-up

Send follow-up messages after missed appointments:

// Run this as a scheduled job after appointment end time
async function checkNoShows() {
  const pastAppointments = await getPastAppointments();
 
  for (const appointment of pastAppointments) {
    if (!appointment.attended && !appointment.followUpSent) {
      const message = `Hi ${appointment.name}, we missed you at your appointment. We'd love to reschedule! Book here: ${rescheduleLink}`;
 
      await sendSMS(appointment.phone, message);
      await markFollowUpSent(appointment.id);
    }
  }
}

Use Cases by Industry

Healthcare

const healthcareTemplates = {
  confirmation: (name, doctor, time) =>
    `Hi ${name}, your appointment with Dr. ${doctor} is confirmed for ${time}. Please arrive 10 minutes early.`,
 
  reminder: (name, time) =>
    `Reminder: Your medical appointment is tomorrow at ${time}. Reply CONFIRM or call us to reschedule.`,
};

Consulting

const consultingTemplates = {
  confirmation: (name, consultant, time) =>
    `Hi ${name}, your consultation with ${consultant} is set for ${time}. We've sent prep materials via email.`,
 
  followUp: (name) =>
    `Thank you ${name} for the great discussion! Your proposal will be ready in 2 business days.`,
};

Sales

const salesTemplates = {
  confirmation: (name, rep, time) =>
    `Hi ${name}! Looking forward to our demo at ${time}. Your sales rep ${rep} will walk you through everything.`,
 
  reminder: (name) =>
    `Quick reminder ${name} - our product demo starts in 1 hour. Got questions? Call us anytime!`,
};

Best Practices

  • Always get consent before sending SMS
  • Keep messages concise - under 160 characters when possible
  • Include opt-out instructions in your first message
  • Test timezone handling for international bookings
  • Store notification preferences in your database
  • Implement retry logic for failed sends
  • Track delivery status for each notification

Monitoring

// Track notification metrics
const metrics = {
  sent: 0,
  delivered: 0,
  failed: 0,
  optOuts: 0,
};
 
async function sendSMSWithTracking(phoneNumber, message, eventId) {
  try {
    metrics.sent++;
    const response = await sendSMS(phoneNumber, message);
    metrics.delivered++;
 
    await logNotification({
      eventId,
      phoneNumber,
      message,
      status: "delivered",
      timestamp: new Date(),
    });
 
    return response;
  } catch (error) {
    metrics.failed++;
    await logNotification({
      eventId,
      phoneNumber,
      message,
      status: "failed",
      error: error.message,
      timestamp: new Date(),
    });
    throw error;
  }
}

Refer to Calendly Webhook Documentation (opens in a new tab) for more information.