Enable your users to connect their calendars to meetergo for automatic availability detection and event syncing.
Why Calendar Connection?
Connected calendars enable:
- Conflict detection - Automatically block times when users have other events
- Event creation - Create calendar events when bookings are made
- Two-way sync - Keep meetergo and external calendars in sync
Supported Providers
| Provider | Features |
|---|
| Google Calendar | Full read/write, multiple calendars |
| Microsoft 365 | Outlook calendar, Teams integration |
Quick Start
Connecting a user’s calendar is a simple 3-step OAuth flow:
Get OAuth URL
Call the API to get the provider’s authorization URL
Redirect User
Send the user to Google/Microsoft to authorize access
Handle Callback
meetergo handles the OAuth callback and redirects to your redirectUrl with the result
Implementation
Step 1: Get OAuth Authorization URL
async function connectCalendar(userId, provider, redirectUri) {
const response = await fetch(
`https://api.meetergo.com/v4/calendar-connections/auth/${provider}/url?` +
new URLSearchParams({
redirectUrl: redirectUri,
setAsCalendarSync: 'true' // Optional: auto-set as primary calendar
}),
{
headers: {
'Authorization': `Bearer ${process.env.MEETERGO_API_KEY}`,
'x-meetergo-api-user-id': userId
}
}
);
const { authUrl } = await response.json();
return authUrl;
}
// Usage
const authUrl = await connectCalendar(
'user-uuid',
'google', // or 'outlook'
'https://yourapp.com/calendar/callback'
);
// Redirect user to OAuth
window.location.href = authUrl;
Step 2: Handle the Callback
After the user authorizes access, they’ll be redirected to your redirectUrl with query parameters:
Success:
https://yourapp.com/callback?success=true&provider=google&connectionId=abc-123
Error:
https://yourapp.com/callback?error=true&provider=google&message=Connection%2520already%2520exists
// On your callback page
function handleCalendarCallback() {
const params = new URLSearchParams(window.location.search);
const success = params.get('success') === 'true';
const error = params.get('error') === 'true';
const provider = params.get('provider');
const connectionId = params.get('connectionId');
const message = params.get('message');
if (success) {
console.log(`Connected ${provider} calendar!`, connectionId);
// Show success message, redirect to settings, etc.
} else if (error) {
console.error(`Failed to connect ${provider}:`, decodeURIComponent(message));
// Show error message with retry option
}
}
API Reference
Get OAuth URL
GET /v4/calendar-connections/auth/{provider}/url
| Parameter | Type | Required | Description |
|---|
provider | path | Yes | google or outlook |
redirectUrl | query | No | URL to redirect after OAuth (defaults to meetergo) |
userId | query | No | Connect for another user (admin only) |
setAsCalendarSync | query | No | Auto-set as primary calendar (true/false) |
Headers:
| Header | Required | Description |
|---|
Authorization | Yes | Bearer <api-key> |
x-meetergo-api-user-id | Yes | User UUID to connect calendar for |
Response:
{
"authUrl": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...",
"provider": "google"
}
List Calendar Connections
GET /v4/calendar-connections/connections
Get all calendar connections for a user:
async function getConnections(userId) {
const response = await fetch(
'https://api.meetergo.com/v4/calendar-connections/connections',
{
headers: {
'Authorization': `Bearer ${process.env.MEETERGO_API_KEY}`,
'x-meetergo-api-user-id': userId
}
}
);
return response.json();
}
Response:
{
"connections": [
{
"id": "conn-123",
"provider": "google",
"email": "user@gmail.com",
"isPrimary": true,
"isConnected": true,
"primaryCalendar": "primary",
"selectedCalendars": ["primary"],
"validateAvailability": true,
"cancelIfDeclinedOrDeleted": true,
"disableAttendeeNotification": false
}
],
"total": 1
}
Get Connection Calendars
GET /v4/calendar-connections/connections/{connectionId}/calendars
Retrieve available calendars for a connection. Use this to let users choose which calendar to write events to and which to check for availability:
async function getCalendars(userId, connectionId) {
const response = await fetch(
`https://api.meetergo.com/v4/calendar-connections/connections/${connectionId}/calendars`,
{
headers: {
'Authorization': `Bearer ${process.env.MEETERGO_API_KEY}`,
'x-meetergo-api-user-id': userId
}
}
);
return response.json();
}
Response:
{
"calendars": [
{ "id": "primary", "name": "Calendar", "isPrimary": true, "canEdit": true, "selected": true },
{ "id": "work@group.calendar.google.com", "name": "Work", "isPrimary": false, "canEdit": true, "selected": false }
],
"connectionId": "conn-123",
"provider": "google"
}
Update Calendar Connection
PATCH /v4/calendar-connections/connections/{connectionId}
Update settings for a connection such as which calendar to create events in, which calendars to check for availability, and notification preferences:
async function updateConnection(userId, connectionId, settings) {
const response = await fetch(
`https://api.meetergo.com/v4/calendar-connections/connections/${connectionId}`,
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${process.env.MEETERGO_API_KEY}`,
'x-meetergo-api-user-id': userId,
'Content-Type': 'application/json'
},
body: JSON.stringify(settings)
}
);
return response.json();
}
// Set which calendar events are created in
await updateConnection(userId, connectionId, {
primaryCalendar: 'work@group.calendar.google.com'
});
// Choose which calendars to check for availability
await updateConnection(userId, connectionId, {
selectedCalendars: ['primary', 'work@group.calendar.google.com']
});
Request body (all fields optional):
| Field | Type | Description |
|---|
primaryCalendar | string | Calendar ID to create new events in |
selectedCalendars | string[] | Calendar IDs to check for availability conflicts |
validateAvailability | boolean | Whether to check this connection for conflicts |
cancelIfDeclinedOrDeleted | boolean | Auto-cancel booking if calendar event is declined |
disableAttendeeNotification | boolean | Suppress attendee notifications on calendar events |
Delete Calendar Connection
DELETE /v4/calendar-connections/connections/{connectionId}
async function disconnectCalendar(userId, connectionId) {
await fetch(
`https://api.meetergo.com/v4/calendar-connections/connections/${connectionId}`,
{
method: 'DELETE',
headers: {
'Authorization': `Bearer ${process.env.MEETERGO_API_KEY}`,
'x-meetergo-api-user-id': userId
}
}
);
}
Set Primary Calendar Connection
POST /v4/calendar-connections/connections/{connectionId}/set-primary
Set which calendar connection should be used for creating new events:
await fetch(
`https://api.meetergo.com/v4/calendar-connections/connections/${connectionId}/set-primary`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MEETERGO_API_KEY}`,
'x-meetergo-api-user-id': userId
}
}
);
Building Your Own UI
Here’s a complete React example for a calendar settings page:
import { useState, useEffect } from 'react';
const PROVIDERS = [
{ id: 'google', name: 'Google Calendar', icon: '📅', color: '#4285F4' },
{ id: 'outlook', name: 'Microsoft 365', icon: '📆', color: '#0078D4' }
];
function CalendarSettings({ userId, apiKey }) {
const [connections, setConnections] = useState([]);
const [loading, setLoading] = useState(true);
const [connecting, setConnecting] = useState(null);
const headers = {
'Authorization': `Bearer ${apiKey}`,
'x-meetergo-api-user-id': userId
};
useEffect(() => {
loadConnections();
handleCallbackParams();
}, []);
async function loadConnections() {
try {
const response = await fetch(
'https://api.meetergo.com/v4/calendar-connections/connections',
{ headers }
);
const data = await response.json();
setConnections(data.connections);
} finally {
setLoading(false);
}
}
function handleCallbackParams() {
const params = new URLSearchParams(window.location.search);
if (params.get('success') === 'true') {
// Clear URL params and reload connections
window.history.replaceState({}, '', window.location.pathname);
loadConnections();
}
}
async function connect(provider) {
setConnecting(provider);
try {
const response = await fetch(
`https://api.meetergo.com/v4/calendar-connections/auth/${provider}/url?` +
new URLSearchParams({
redirectUrl: window.location.href,
setAsCalendarSync: 'true'
}),
{ headers }
);
const { authUrl } = await response.json();
window.location.href = authUrl;
} catch (error) {
alert('Failed to start connection. Please try again.');
setConnecting(null);
}
}
async function disconnect(connectionId) {
if (!confirm('Disconnect this calendar?')) return;
await fetch(
`https://api.meetergo.com/v4/calendar-connections/connections/${connectionId}`,
{ method: 'DELETE', headers }
);
loadConnections();
}
if (loading) {
return <div className="loading">Loading calendar connections...</div>;
}
return (
<div className="calendar-settings">
<h2>Calendar Connections</h2>
<p className="description">
Connect your calendar to automatically check for conflicts and
create events when meetings are booked.
</p>
<div className="providers">
{PROVIDERS.map(provider => {
const connection = connections.find(c => c.provider === provider.id);
const isConnecting = connecting === provider.id;
return (
<div key={provider.id} className="provider-card">
<div className="provider-info">
<span className="icon">{provider.icon}</span>
<div>
<strong>{provider.name}</strong>
{connection && (
<span className="email">{connection.email}</span>
)}
</div>
</div>
{connection ? (
<div className="actions">
<span className="status connected">Connected</span>
<button
onClick={() => disconnect(connection.id)}
className="btn-secondary"
>
Disconnect
</button>
</div>
) : (
<button
onClick={() => connect(provider.id)}
disabled={isConnecting}
className="btn-primary"
style={{ backgroundColor: provider.color }}
>
{isConnecting ? 'Connecting...' : 'Connect'}
</button>
)}
</div>
);
})}
</div>
</div>
);
}
export default CalendarSettings;
.calendar-settings {
max-width: 600px;
margin: 0 auto;
padding: 24px;
}
.description {
color: #666;
margin-bottom: 24px;
}
.providers {
display: flex;
flex-direction: column;
gap: 16px;
}
.provider-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border: 1px solid #e0e0e0;
border-radius: 8px;
background: #fff;
}
.provider-info {
display: flex;
align-items: center;
gap: 12px;
}
.provider-info .icon {
font-size: 24px;
}
.provider-info .email {
display: block;
font-size: 14px;
color: #666;
}
.actions {
display: flex;
align-items: center;
gap: 12px;
}
.status.connected {
color: #22c55e;
font-weight: 500;
}
.btn-primary {
padding: 8px 16px;
border: none;
border-radius: 6px;
color: white;
font-weight: 500;
cursor: pointer;
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-secondary {
padding: 8px 16px;
border: 1px solid #e0e0e0;
border-radius: 6px;
background: white;
cursor: pointer;
}
.btn-secondary:hover {
background: #f5f5f5;
}
For a better UX, open OAuth in a popup window instead of redirecting:
function connectCalendarPopup(userId, provider, apiKey) {
return new Promise(async (resolve, reject) => {
// Get OAuth URL
const response = await fetch(
`https://api.meetergo.com/v4/calendar-connections/auth/${provider}/url?` +
new URLSearchParams({
redirectUrl: `${window.location.origin}/calendar-callback.html`
}),
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'x-meetergo-api-user-id': userId
}
}
);
const { authUrl } = await response.json();
// Open popup
const popup = window.open(
authUrl,
'calendar-connect',
'width=600,height=700,left=100,top=100'
);
// Listen for callback message
const handleMessage = (event) => {
if (event.origin !== window.location.origin) return;
if (event.data.type === 'calendar-connected') {
window.removeEventListener('message', handleMessage);
popup.close();
if (event.data.success) {
resolve(event.data.connectionId);
} else {
reject(new Error(event.data.error));
}
}
};
window.addEventListener('message', handleMessage);
// Handle popup close
const checkClosed = setInterval(() => {
if (popup.closed) {
clearInterval(checkClosed);
window.removeEventListener('message', handleMessage);
reject(new Error('Popup closed'));
}
}, 1000);
});
}
callback page (calendar-callback.html):
<!DOCTYPE html>
<html>
<head><title>Connecting...</title></head>
<body>
<script>
const params = new URLSearchParams(window.location.search);
window.opener.postMessage({
type: 'calendar-connected',
success: params.get('success') === 'true',
connectionId: params.get('connectionId'),
error: params.get('message') ? decodeURIComponent(params.get('message')) : null
}, window.location.origin);
</script>
</body>
</html>
Error Handling
| Error | Cause | Solution |
|---|
Connection already exists | User already connected this account | Show message that account is already connected |
access_denied | User declined OAuth permission | Show message explaining why calendar access is needed |
invalid_scope | Missing required permissions | Check OAuth configuration |
server_error | Provider API error | Retry after a few seconds |
user_not_found | Invalid x-meetergo-api-user-id | Verify user exists in your meetergo account |
Best Practices
Explain the benefits - Tell users why connecting their calendar improves their experience
Handle errors gracefully - OAuth can fail; show helpful messages and retry options
Show connection status - Let users see which calendars are connected and manage them
Use popup flow - Better UX than full-page redirect, keeps user context
Never expose API keys in frontend code - Make API calls from your backend, not the browser
Validate redirect URLs - Only allow redirects to your own domain
Next Steps
Availability
Configure user availability schedules
Calendar Sync Recipe
Sync bookings to external calendars via webhooks
Webhooks
Get notified when bookings are created or changed
API Reference
Full API documentation