Calendly Connector
Connect Calendly to Brevo through Tajo to automatically sync meeting invitees as contacts, trigger email sequences based on booking events, and streamline your sales and onboarding workflows.
Overview
| Property | Value |
|---|---|
| Platform | Calendly |
| Category | Scheduling (Custom) |
| Setup Complexity | Easy |
| Official Integration | No |
| Data Synced | Events, Contacts, Bookings, Cancellations |
| Auth Method | OAuth 2.0 / Personal Access Token |
Features
- Invitee sync - Automatically create Brevo contacts from meeting invitees
- Booking triggers - Fire Brevo automations when meetings are booked
- Cancellation handling - Trigger re-engagement flows on cancellations
- No-show detection - Update contact status when invitees miss meetings
- Event type mapping - Map different Calendly event types to Brevo lists
- Scheduling API - Build scheduling directly into your app without redirects
Prerequisites
Before you begin, ensure you have:
- A Calendly account (Professional plan or above for API access)
- A Personal Access Token from Calendly Integrations
- A Brevo account with API access
- A Tajo account with connector permissions
Authentication
Personal Access Token
# Generate at https://calendly.com/integrations/api_webhooksexport CALENDLY_ACCESS_TOKEN=your_personal_access_tokenexport TAJO_API_KEY=your_tajo_api_keyexport BREVO_API_KEY=your_brevo_api_keyOAuth 2.0
// OAuth 2.0 Authorization Code Flowconst authUrl = 'https://auth.calendly.com/oauth/authorize?' + new URLSearchParams({ client_id: process.env.CALENDLY_CLIENT_ID, redirect_uri: 'https://your-app.com/callback', response_type: 'code' });
// Exchange code for tokenconst tokenResponse = await fetch('https://auth.calendly.com/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', code: authorizationCode, client_id: process.env.CALENDLY_CLIENT_ID, client_secret: process.env.CALENDLY_CLIENT_SECRET, redirect_uri: 'https://your-app.com/callback' })});Configuration
Basic Setup
connectors: calendly: enabled: true access_token: "${CALENDLY_ACCESS_TOKEN}"
sync: contacts: true events: true cancellations: true
event_mapping: discovery_call: list_id: 10 event_type_uri: "https://api.calendly.com/event_types/abc123" demo: list_id: 11 event_type_uri: "https://api.calendly.com/event_types/xyz789"
webhook: signing_key: "${CALENDLY_WEBHOOK_SIGNING_KEY}"Field Mapping
field_mapping: email: email name: FIRSTNAME questions_and_answers: company: COMPANY role: JOB_TITLE phone: SMS event_type_name: CALENDLY_EVENT_TYPE scheduled_at: MEETING_DATE status: BOOKING_STATUSAPI Endpoints
| Endpoint | Method | Description |
|---|---|---|
https://api.calendly.com/users/me | GET | Get current user |
https://api.calendly.com/event_types | GET | List event types |
https://api.calendly.com/scheduled_events | GET | List scheduled events |
https://api.calendly.com/scheduled_events/{uuid} | GET | Get a scheduled event |
https://api.calendly.com/scheduled_events/{uuid}/invitees | GET | List invitees |
https://api.calendly.com/scheduling_links | POST | Create scheduling link |
https://api.calendly.com/webhook_subscriptions | POST | Create webhook |
https://api.calendly.com/webhook_subscriptions | GET | List webhooks |
https://api.calendly.com/invitee_no_shows/{uuid} | GET | Get no-show status |
Code Examples
Initialize Connector
import { TajoClient } from '@tajo/sdk';
const tajo = new TajoClient({ apiKey: process.env.TAJO_API_KEY, brevoApiKey: process.env.BREVO_API_KEY});
await tajo.connectors.connect('calendly', { accessToken: process.env.CALENDLY_ACCESS_TOKEN});List Scheduled Events
// Retrieve scheduled eventsconst response = await fetch( 'https://api.calendly.com/scheduled_events?' + new URLSearchParams({ user: 'https://api.calendly.com/users/YOUR_USER_ID', min_start_time: '2024-01-01T00:00:00Z', max_start_time: '2024-12-31T23:59:59Z', status: 'active', count: 100 }), { headers: { 'Authorization': `Bearer ${process.env.CALENDLY_ACCESS_TOKEN}`, 'Content-Type': 'application/json' } });
const events = await response.json();Sync Invitees to Brevo
// Get invitees for a scheduled event and sync to Brevoconst inviteesResponse = await fetch( `https://api.calendly.com/scheduled_events/${eventUuid}/invitees`, { headers: { 'Authorization': `Bearer ${process.env.CALENDLY_ACCESS_TOKEN}` } });
const { collection } = await inviteesResponse.json();
for (const invitee of collection) { await tajo.contacts.sync({ email: invitee.email, attributes: { FIRSTNAME: invitee.name, CALENDLY_EVENT_TYPE: invitee.event, MEETING_DATE: invitee.created_at, BOOKING_STATUS: invitee.status }, listIds: [10] });}Set Up Webhook Subscriptions
// Subscribe to Calendly eventsconst webhook = await fetch( 'https://api.calendly.com/webhook_subscriptions', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.CALENDLY_ACCESS_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://api.tajo.io/webhooks/calendly', events: [ 'invitee.created', 'invitee.canceled', 'invitee_no_show.created' ], organization: 'https://api.calendly.com/organizations/YOUR_ORG_ID', scope: 'organization', signing_key: process.env.CALENDLY_WEBHOOK_SIGNING_KEY }) });Handle Webhook Events
app.post('/webhooks/calendly', async (req, res) => { // Verify webhook signature const signature = req.headers['calendly-webhook-signature']; const isValid = verifyCalendlySignature( req.rawBody, signature, process.env.CALENDLY_WEBHOOK_SIGNING_KEY );
if (!isValid) return res.status(401).send('Unauthorized');
const { event, payload } = req.body;
switch (event) { case 'invitee.created': await tajo.contacts.sync({ email: payload.email, attributes: { BOOKING_STATUS: 'booked' }, listIds: [10] }); break; case 'invitee.canceled': await tajo.contacts.update(payload.email, { attributes: { BOOKING_STATUS: 'cancelled' } }); break; case 'invitee_no_show.created': await tajo.contacts.update(payload.email, { attributes: { BOOKING_STATUS: 'no_show' } }); break; }
res.status(200).send('OK');});Rate Limits
| Resource | Limit | Notes |
|---|---|---|
| API requests | 6,000/min | Organization-wide limit |
| Webhook subscriptions | 30 per organization | Across all event types |
| Scheduling links | Unlimited | No per-minute limit |
Pagination
Calendly API responses use cursor-based pagination. Use the next_page_token from the pagination object to retrieve additional results. The default page size is 20 items, with a maximum of 100.
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Webhook not received | Wrong scope | Use organization scope for webhooks |
| 401 Unauthorized | Token expired | Generate new token or refresh OAuth token |
| Missing invitee data | Questions not configured | Add custom questions to event type |
| Duplicate contacts | No dedup logic | Use email as unique identifier for upserts |
| Rate limit 429 | Too many requests | Implement backoff and request batching |
Debug Mode
connectors: calendly: debug: true log_level: verbose log_webhooks: trueBest Practices
- Use webhooks - Subscribe to
invitee.createdandinvitee.canceledfor real-time sync - Add custom questions - Collect company, role, and phone data for richer contact profiles
- Map event types - Assign different Brevo lists per Calendly event type
- Handle no-shows - Track no-shows to adjust lead scoring and follow-up sequences
- Use scheduling links - Generate unique scheduling links for personalized booking experiences
- Set organization scope - Use org-level webhooks to capture events from all team members
Security
- OAuth 2.0 - Scoped token-based authentication
- Webhook signatures - HMAC signature validation for incoming webhooks
- HTTPS only - All API endpoints require TLS encryption
- Token expiry - OAuth tokens expire and require refresh flows
- Minimal scopes - Request only required OAuth scopes
- Secure storage - Store tokens in environment variables or secret managers
Related Resources
Open-Source Implementation Map
No official open-source repository was found in the current Tajo connector catalog for Calendly. Keep this page focused on the verified public API contract and vendor documentation until an official schema, SDK, MCP server, or public integration repository is available.
Tajo Revamp Checklist
- Verify authentication and scope requirements against the vendor documentation before each connector release.
- Document primary sync objects, external IDs, pagination strategy, and rate limits explicitly.
- Add smoke tests from public API examples rather than undocumented behavior.
- Capture webhook signature verification and replay protection when the vendor supports webhooks.
- Record gaps where no official public repository or schema exists so future maintainers know what still needs source-backed validation.