How to Implement Calendly Integration into Your Web Application
Integrating Calendly into your web application can streamline meeting scheduling and automatically sync calendar events with your CRM or internal systems. This guide walks through the complete implementation process, from OAuth authentication to webhook processing.
Overview
A complete Calendly integration involves three main phases:
- OAuth Authentication - Authorize your application to access Calendly data
- Webhook Subscription - Set up real-time notifications for calendar events
- Webhook Processing - Receive and process event data in your application
Phase 1: Setting Up Your Calendly Application
Create a Developer Account
First, you’ll need to create a Calendly developer account and register your application:
Sign up for a developer account at Calendly Developer Console
Create a new OAuth application and configure:
- Application Name: Your app’s name
- Application Type: Web or Native
- Environment: Start with Sandbox for development, then create a Production app when ready
- Redirect URI:
- Sandbox: HTTP with localhost is allowed (e.g.,
http://localhost:1234
) - Production: Must use HTTPS (e.g.,
https://yourdomain.com/auth/calendly
)
- Sandbox: HTTP with localhost is allowed (e.g.,
Save your credentials - you’ll receive:
- Client ID
- Client Secret
- Webhook Signing Key (critical for security - you can only view this once!)
Documentation: Create a Developer Account
Phase 2: Implementing OAuth Authentication
Step 1: Get Authorization Code
Redirect users to Calendly’s authorization page to grant access to your application:
https://auth.calendly.com/oauth/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https://yourdomain.com/auth/calendly
Parameters:
client_id
- Your application’s Client IDresponse_type
- Always “code”redirect_uri
- Must match the URI registered in your app settings
When users grant access, Calendly redirects to your specified URI with the authorization code:
https://yourdomain.com/auth/calendly?code=f04281d639d8248435378b0365de7bd1f53bf452eda187d5f1e07ae7f04546d6
Documentation: Get Authorization Code
Step 2: Exchange Code for Access Token
Make a POST request to exchange the authorization code for an access token:
Endpoint: POST https://auth.calendly.com/oauth/token
Request Body:
{
"grant_type": "authorization_code",
"code": "AUTHORIZATION_CODE_FROM_STEP_1",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"redirect_uri": "https://yourdomain.com/auth/calendly"
}
Response:
{
"access_token": "eyJraWQiOiIxY2UxZTEzNjE...",
"refresh_token": "eyJraWQiOiIxY2UxZTEzNjE...",
"token_type": "Bearer",
"expires_in": 7200,
"scope": "default"
}
Store the access token securely - you’ll need it for all subsequent API calls.
Phase 3: Creating a Webhook Subscription
Subscribe to Calendar Events
Create a webhook subscription to receive real-time notifications when meetings are scheduled, canceled, or modified.
Endpoint: POST https://api.calendly.com/webhook_subscriptions
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Request Body:
{
"url": "https://yourdomain.com/api/webhook/calendly",
"events": [
"invitee.created",
"invitee.canceled",
"invitee_no_show.created",
"invitee_no_show.deleted"
],
"organization": "https://api.calendly.com/organizations/AAAAAAAAAAAAAAAA",
"user": "https://api.calendly.com/users/BBBBBBBBBBBBBBBB",
"scope": "user",
"signing_key": "YOUR_WEBHOOK_SIGNING_KEY"
}
Key Parameters:
url
- Your server endpoint that will receive webhook notificationsevents
- Array of events to subscribe to:invitee.created
- New meeting bookingsinvitee.canceled
- Meeting cancellationsinvitee_no_show.created
- No-show eventsrouting_form_submission.created
- Routing form submissions (organization scope only)
scope
- Either “user” (individual events) or “organization” (all team events)signing_key
- Your webhook signing key for payload verification
Documentation:
Phase 4: Processing Webhook Notifications
Webhook Payload Structure
When an event occurs, Calendly sends a POST request to your webhook URL with this structure:
{
"created_at": "2020-11-23T17:51:19.000000Z",
"created_by": "https://api.calendly.com/users/AAAAAAAAAAAAAAAA",
"event": "invitee.created",
"payload": {
"uri": "https://api.calendly.com/scheduled_events/AAAAAAAAAAAAAAAA/invitees/AAAAAAAAAAAAAAAA",
"email": "test@example.com",
"name": "John Doe",
"status": "active",
"timezone": "America/New_York",
"scheduled_event": {
"uri": "https://api.calendly.com/scheduled_events/GBGBDCAADAEDCRZ2",
"name": "15 Minute Meeting",
"status": "active",
"start_time": "2019-08-24T14:15:22.123456Z",
"end_time": "2019-08-24T14:15:22.123456Z",
"location": {
"type": "physical",
"location": "string"
},
"event_memberships": [
{
"user": "https://api.calendly.com/users/GBGBDCAADAEDCRZ2",
"user_email": "user@example.com",
"user_name": "John Smith"
}
],
"event_guests": []
}
}
}
Documentation: Webhook Payload
Verifying Webhook Signatures
Critical: Always verify webhook signatures to ensure requests are authentic and haven’t been tampered with.
Calendly includes a Calendly-Webhook-Signature
header with each webhook:
Calendly-Webhook-Signature: t=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Verification Steps:
- Extract the timestamp (
t
) and signature (v1
) from the header - Create the signed payload:
timestamp + "." + JSON.stringify(requestBody)
- Compute HMAC-SHA256 hash using your signing key
- Compare the computed signature with the received signature
- Verify the timestamp is within your tolerance window (e.g., 3 minutes)
Example Implementation (Node.js):
const crypto = require('crypto');
const webhookSigningKey = process.env.WEBHOOK_SIGNING_KEY;
const calendlySignature = req.get('Calendly-Webhook-Signature');
// Parse signature header
const { t, signature } = calendlySignature.split(',').reduce((acc, current) => {
const [key, value] = current.split('=');
if (key === 't') acc.t = value;
if (key === 'v1') acc.signature = value;
return acc;
}, { t: '', signature: '' });
// Create signed payload
const data = t + '.' + JSON.stringify(req.body);
// Compute expected signature
const expectedSignature = crypto
.createHmac('sha256', webhookSigningKey)
.update(data, 'utf8')
.digest('hex');
// Verify signature matches
if (expectedSignature !== signature) {
throw new Error('Invalid Signature');
}
// Prevent replay attacks - reject old timestamps
const threeMinutes = 180000;
const timestampMilliseconds = Number(t) * 1000;
if (timestampMilliseconds < Date.now() - threeMinutes) {
throw new Error('Signature timestamp outside tolerance zone');
}
Documentation: Webhook Signatures
Phase 5: Processing Event Data
Extracting Useful Information
From the webhook payload, you can extract:
Event Details:
- Meeting title (
payload.scheduled_event.name
) - Start/end times (
payload.scheduled_event.start_time
,end_time
) - Location (
payload.scheduled_event.location
) - Meeting notes (
payload.scheduled_event.meeting_notes_plain
)
Invitee Information:
- Name and email (
payload.name
,payload.email
) - First/last name (if configured separately)
- Timezone (
payload.timezone
) - Custom question responses (
payload.questions_and_answers
)
Host/Attendee Details:
- Event memberships (
payload.scheduled_event.event_memberships
) - Additional guests (
payload.scheduled_event.event_guests
)
Common Implementation Patterns
1. Contact Management: Create or update contacts in your CRM from invitee data
2. Calendar Synchronization: Store events in your database with:
- URI-based unique identification (handles rescheduling)
- IsActive flags for soft deletion of canceled events
- Proper host/invitee relationship tracking
3. Activity Logging: Generate activity records for all event participants
4. User Interface Integration: Display events in:
- Main calendar views
- Contact activity feeds
- Dashboard widgets
Architecture Recommendations
Backend Components
1. Webhook Controller:
- Validate signatures
- Deserialize payloads
- Route to appropriate handlers
2. Event Manager:
- Process event lifecycle (create, reschedule, cancel)
- Manage recipients and relationships
- Handle URI-based event tracking
3. Data Layer:
- Store calendar events
- Track event recipients (hosts, invitees, guests)
- Maintain integration settings
Database Schema
Consider these tables:
CalendarEvent:
- URI (unique identifier)
- Title, start/end times, location
- Integration source
- IsActive flag for soft deletion
CalendarEventRecipient:
- Event relationship
- Contact information
- Role (host/invitee/guest)
IntegrationSite:
- User/organization association
- OAuth credentials
- Webhook signing keys (encrypted)
Security Best Practices
- Always verify webhook signatures - Never trust unverified webhooks
- Implement replay attack prevention - Use timestamp validation
- Encrypt sensitive data - Store API keys and signing keys encrypted
- Use HTTPS - Required for production webhook endpoints
- Rate limiting - Implement rate limits on your webhook endpoint
- Error handling - Log errors but avoid exposing sensitive information
Testing Your Integration
Development Tips
- Start with Calendly’s Sandbox environment for testing
- Use tools like ngrok to expose localhost for webhook testing
- Log all webhook payloads during development
- Test all event types: create, reschedule, cancel, no-show
Common Gotchas
- Rescheduling creates new events: Use URI-based tracking to link rescheduled events
- Signature verification timing: Always validate before processing
- Timezone handling: Store times in UTC, display in user’s timezone
- Multiple guests: Process both event_memberships and event_guests arrays
Conclusion
Implementing Calendly integration provides seamless calendar synchronization and eliminates manual data entry. By following OAuth best practices, properly verifying webhooks, and handling all event types, you can build a robust integration that enhances your application’s scheduling capabilities.
Key Takeaways
- Register your app in Calendly Developer Console
- Implement proper OAuth flow with secure token storage
- Create webhook subscriptions for real-time event notifications
- Always verify webhook signatures to ensure authenticity
- Handle all event lifecycle stages (create, reschedule, cancel)
- Store events with URI-based unique identification
Additional Resources
Happy integrating! 🚀