Integrations

Connect Google Calendar, Outlook, and other calendar services.

Overview

Calendar integrations provide two-way sync with external calendar services. Prevent double-bookings and automatically create events in connected calendars.

Supported Integrations

ServiceSync TypeVideo Meetings
Google CalendarTwo-wayGoogle Meet
Microsoft OutlookTwo-wayMicrosoft Teams
Apple CalendarCalDAV-
ZoomOne-wayZoom Meetings

Google Calendar

Connect Google Calendar

  1. Generate OAuth URL:
const authUrl = await client.calendar.integrations.getAuthUrl({
  provider: 'GOOGLE',
  redirectUri: 'https://yourapp.com/callback/google',
  scopes: ['calendar', 'calendar.events'],
});
 
// Redirect user to authUrl
  1. Handle callback:
app.get('/callback/google', async (req, res) => {
  const { code } = req.query;
 
  const connection = await client.calendar.integrations.connect({
    provider: 'GOOGLE',
    code: code,
    redirectUri: 'https://yourapp.com/callback/google',
  });
 
  console.log('Connected Google Calendar:', connection.id);
  res.redirect('/settings/calendars');
});

Configure Google Sync

await client.calendar.integrations.configure(connection.id, {
  // Sync settings
  syncDirection: 'BOTH', // 'INBOUND', 'OUTBOUND', 'BOTH'
  syncFrequency: 5, // Minutes
 
  // Calendar selection
  calendarsToSync: ['primary', 'calendar_id_2'],
 
  // Busy time blocking
  blockBusyTime: true,
  busyCalendars: ['primary'],
 
  // Event creation
  createEventsIn: 'primary',
 
  // Privacy
  showEventDetails: false, // Hide details in external calendar
});

Automatically generate Meet links:

const eventType = await client.calendar.eventTypes.create({
  title: 'Video Call',
  location: {
    type: 'GOOGLE_MEET',
    // Meet links auto-generated when booked
  },
});

Microsoft Outlook / Office 365

Connect Outlook

const authUrl = await client.calendar.integrations.getAuthUrl({
  provider: 'MICROSOFT',
  redirectUri: 'https://yourapp.com/callback/microsoft',
  scopes: ['Calendars.ReadWrite', 'OnlineMeetings.ReadWrite'],
});

Handle Callback

app.get('/callback/microsoft', async (req, res) => {
  const { code } = req.query;
 
  const connection = await client.calendar.integrations.connect({
    provider: 'MICROSOFT',
    code: code,
    redirectUri: 'https://yourapp.com/callback/microsoft',
  });
 
  res.redirect('/settings/calendars');
});
const eventType = await client.calendar.eventTypes.create({
  title: 'Teams Meeting',
  location: {
    type: 'TEAMS',
    // Teams links auto-generated
  },
});

Apple Calendar (CalDAV)

Connect Apple Calendar

const connection = await client.calendar.integrations.connect({
  provider: 'CALDAV',
  serverUrl: 'https://caldav.icloud.com',
  username: 'user@icloud.com',
  password: 'app-specific-password',
});

Zoom Integration

Connect Zoom

const authUrl = await client.calendar.integrations.getAuthUrl({
  provider: 'ZOOM',
  redirectUri: 'https://yourapp.com/callback/zoom',
});

Configure Zoom

await client.calendar.integrations.configure(connection.id, {
  defaultMeetingSettings: {
    joinBeforeHost: true,
    muteUponEntry: true,
    waitingRoom: true,
    autoRecording: 'none', // 'cloud', 'local', 'none'
  },
});

Use Zoom for Events

const eventType = await client.calendar.eventTypes.create({
  title: 'Zoom Call',
  location: {
    type: 'ZOOM',
    // Zoom links auto-generated
  },
});

Managing Connections

List Connections

const connections = await client.calendar.integrations.list();
 
for (const conn of connections) {
  console.log(`${conn.provider}: ${conn.status}`);
  console.log(`  Calendars: ${conn.calendars.length}`);
  console.log(`  Last sync: ${conn.lastSyncAt}`);
}

Check Connection Status

const connection = await client.calendar.integrations.get(connectionId);
 
if (connection.status === 'ERROR') {
  console.log('Connection error:', connection.error);
  // May need to re-authenticate
}

Disconnect

await client.calendar.integrations.disconnect(connectionId);

Refresh Token

// Manually refresh if needed
await client.calendar.integrations.refresh(connectionId);

Sync Behavior

Inbound Sync (External → Transactional)

External calendar events block availability:

await client.calendar.integrations.configure(connectionId, {
  syncDirection: 'INBOUND',
  blockBusyTime: true,
  busyCalendars: ['primary', 'work'],
});

Outbound Sync (Transactional → External)

Bookings create events in external calendar:

await client.calendar.integrations.configure(connectionId, {
  syncDirection: 'OUTBOUND',
  createEventsIn: 'primary',
  eventDefaults: {
    visibility: 'private',
    reminders: [
      { method: 'popup', minutes: 15 },
    ],
  },
});

Two-Way Sync

Both directions:

await client.calendar.integrations.configure(connectionId, {
  syncDirection: 'BOTH',
  blockBusyTime: true,
  createEventsIn: 'primary',
});

Conflict Detection

When syncing multiple calendars, detect conflicts:

const conflicts = await client.calendar.availability.getConflicts({
  eventTypeId: 'evt_xxx',
  startDate: '2024-01-15',
  endDate: '2024-01-22',
});
 
for (const conflict of conflicts) {
  console.log(`Conflict: ${conflict.startTime}`);
  console.log(`  Reason: ${conflict.source} - ${conflict.title}`);
}

Calendar Selection

List Available Calendars

After connecting, list accessible calendars:

const calendars = await client.calendar.integrations.listCalendars(connectionId);
 
for (const cal of calendars) {
  console.log(`${cal.name} (${cal.id})`);
  console.log(`  Primary: ${cal.isPrimary}`);
  console.log(`  Color: ${cal.color}`);
}

Select Calendars to Sync

await client.calendar.integrations.configure(connectionId, {
  calendarsToSync: [
    'primary',
    'calendar_id_work',
    // Exclude personal calendar
  ],
});

Event Details Privacy

Control what appears in external calendars:

await client.calendar.integrations.configure(connectionId, {
  eventPrivacy: {
    showTitle: true,           // Show event title
    showDescription: false,    // Hide description
    showAttendees: false,      // Hide attendee info
    showLocation: true,        // Show meeting link
  },
});

Webhooks for Sync Events

Monitor sync status:

app.post('/webhooks/calendar', (req, res) => {
  const event = req.body;
 
  switch (event.type) {
    case 'integration.synced':
      console.log('Sync completed:', event.data.connectionId);
      break;
 
    case 'integration.error':
      console.log('Sync error:', event.data.error);
      alertAdmin(event.data);
      break;
 
    case 'integration.disconnected':
      console.log('Calendar disconnected');
      notifyUser(event.data.userId);
      break;
  }
 
  res.status(200).send('OK');
});

Troubleshooting

Token Expired

const connection = await client.calendar.integrations.get(connectionId);
 
if (connection.status === 'TOKEN_EXPIRED') {
  // Redirect user to re-authenticate
  const authUrl = await client.calendar.integrations.getAuthUrl({
    provider: connection.provider,
    connectionId: connectionId, // Re-authenticate existing connection
    redirectUri: '...',
  });
}

Sync Conflicts

// Force resync
await client.calendar.integrations.resync(connectionId, {
  fullSync: true, // Full sync vs incremental
});

Rate Limits

External APIs have rate limits. The system handles this automatically but you can check status:

const status = await client.calendar.integrations.getSyncStatus(connectionId);
 
console.log('Sync status:', status.state);
console.log('Next sync:', status.nextSyncAt);
if (status.rateLimited) {
  console.log('Rate limited until:', status.rateLimitResetsAt);
}

Next Steps