Debugging Calendar Issues
Troubleshooting guide for calendar OAuth connections, availability, and booking problems.
OAuth Token Expired
Symptoms: Calendar availability returns errors. Booking pages show "No times available" when there should be availability. Edge function logs show 401 from Google/Outlook.
Diagnose:
-- Check client calendar tokens
SELECT client_id, provider, connected_email, token_expires_at,
token_expires_at < now() AS is_expired
FROM calendar_connections
WHERE is_active = true;
-- Check internal user calendar tokens
SELECT user_id, provider, connected_email, token_expires_at,
token_expires_at < now() AS is_expired
FROM user_calendar_connections
WHERE is_active = true;
Fix:
Tokens are normally refreshed automatically when the get-calendar-availability or create-calendar-event edge functions run. But if a token has been expired for too long, the refresh token may also be invalid.
The user or client needs to re-authorize:
- Navigate to account/scheduling settings
- Click Disconnect on the current calendar connection
- Click Connect Google Calendar or Connect Outlook Calendar
- Complete the OAuth flow
The callback handler will store fresh access and refresh tokens.
Wrong Calendar Table
Symptoms: Code queries for a calendar connection and gets no results, even though the user has connected their calendar.
Cause: The system uses two different tables depending on the booking mode:
| Mode | Calendar Table | How to Identify |
|---|---|---|
| Client mode | calendar_connections | booking_links.client_id IS NOT NULL |
| Internal (user) mode | user_calendar_connections | booking_links.user_id IS NOT NULL AND booking_links.client_id IS NULL |
Diagnose:
First, determine the mode of the booking link:
SELECT id, slug, client_id, user_id,
CASE
WHEN client_id IS NOT NULL THEN 'client'
WHEN user_id IS NOT NULL AND client_id IS NULL THEN 'internal'
END AS mode
FROM booking_links
WHERE slug = '<booking-slug>';
Then check the correct table:
-- For client mode
SELECT * FROM calendar_connections WHERE client_id = '<client-id>';
-- For internal mode
SELECT * FROM user_calendar_connections WHERE user_id = '<user-id>';
In code:
const isInternalLink = !bookingLink?.client_id && !!bookingLink?.user_id;
if (isInternalLink) {
// Query user_calendar_connections
} else {
// Query calendar_connections
}
OAuth State Parsing
Symptoms: OAuth callback fails, redirects to the wrong page, or creates a connection in the wrong table.
Cause: The OAuth state parameter encodes the mode and owner ID. If parsed incorrectly, tokens are stored in the wrong table.
State format:
| Mode | State Format | Example |
|---|---|---|
| Client | {clientId}:{provider} | abc123:google |
| Internal | user:{userId}:{provider} | user:def456:outlook |
Parsing logic in CalendarCallback.tsx:
const parts = state.split(':');
if (parts[0] === 'user') {
// Internal mode: parts[1] = userId, parts[2] = provider
const userId = parts[1];
const provider = parts[2];
// Store in user_calendar_connections
// Redirect to /admin/sales/scheduling
} else {
// Client mode: parts[0] = clientId, parts[1] = provider
const clientId = parts[0];
const provider = parts[1];
// Store in calendar_connections
// Redirect to /workspace/{clientId}/client-info
}
If callbacks fail:
- Check the edge function logs for
google-calendar-callbackoroutlook-calendar-callback - Verify the OAuth redirect URI matches what is configured in the Google/Outlook app settings
- Check the state parameter is being passed through correctly (some OAuth providers strip state on error redirects)
Availability Showing Wrong Times
Symptoms: Booking page shows incorrect available times, times that should be blocked, or no times at all.
Possible causes:
1. Availability settings not configured
-- For client mode
SELECT availability_settings, timezone
FROM clients
WHERE id = '<client-id>';
-- For internal mode
SELECT availability_settings, timezone
FROM user_profiles
WHERE id = '<user-id>';
If availability_settings is NULL, no times will show as available. The settings JSON should look like:
{
"monday": { "enabled": true, "start": "09:00", "end": "17:00" },
"tuesday": { "enabled": true, "start": "09:00", "end": "17:00" },
"wednesday": { "enabled": true, "start": "09:00", "end": "17:00" },
"thursday": { "enabled": true, "start": "09:00", "end": "17:00" },
"friday": { "enabled": true, "start": "09:00", "end": "17:00" },
"saturday": { "enabled": false },
"sunday": { "enabled": false }
}
2. Wrong timezone
-- Check timezone setting
SELECT timezone FROM user_profiles WHERE id = '<user-id>';
SELECT timezone FROM clients WHERE id = '<client-id>';
Default is America/New_York. If the user is in a different timezone but the setting was not updated, availability windows will be offset.
3. Calendar conflict check failing
The get-calendar-availability edge function checks the connected calendar for existing events. If the calendar API call fails (e.g., expired token), it may block all times or show all times as available depending on the error handling.
Check edge function logs for get-calendar-availability.
4. Querying the wrong availability source
| Mode | Availability Source |
|---|---|
| Client | clients.availability_settings |
| Internal | user_profiles.availability_settings |
If the edge function queries the wrong table, it will get NULL settings.
Booking Link 404
Symptoms: Visiting /book/<slug> returns a 404 or blank page.
Checklist:
-
Verify slug exists:
SELECT id, slug, is_active, client_id, user_id
FROM booking_links
WHERE slug = '<slug>'; -
Check
is_active: Iffalse, the link is deactivated and will not resolve. -
Check the routing: The
BookingPage.tsxcomponent loads booking links by slug. Verify the route is correctly defined inApp.tsx.
Meeting Creation Fails
Symptoms: Prospect books a time on the booking page, but no meeting record is created or no calendar event appears.
Diagnose:
-
Check edge function logs for
create-calendar-event-- look for errors -
Verify calendar connection is active:
-- For internal booking links
SELECT * FROM user_calendar_connections
WHERE user_id = '<user-id>' AND is_active = true; -
Check the meeting table:
-- For internal meetings
SELECT * FROM sales_meetings
WHERE booking_link_id = '<booking-link-id>'
ORDER BY created_at DESC LIMIT 5;
-- For client meetings
SELECT * FROM meetings
WHERE booking_link_id = '<booking-link-id>'
ORDER BY created_at DESC LIMIT 5; -
Check notification delivery: If the meeting was created but no email was sent, check
send-booking-notificationedge function logs.