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
- Log in to Calendly and navigate to Integrations > API & Webhooks
- Click Create Webhook Subscription
- Enter your webhook URL (e.g.,
https://yourdomain.com/calendly/webhook) - Select the events you want to receive:
invitee.createdinvitee.canceledinvitee.rescheduled
- Copy the Webhook Signing Key and add it to your environment variables
Step 3: Add Phone Number Field
In your Calendly event settings:
- Go to Event Types and select your event
- Add a custom question: "Phone Number"
- Make it required for booking
- 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.