Skip to main content

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:

  1. Navigate to account/scheduling settings
  2. Click Disconnect on the current calendar connection
  3. Click Connect Google Calendar or Connect Outlook Calendar
  4. 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:

ModeCalendar TableHow to Identify
Client modecalendar_connectionsbooking_links.client_id IS NOT NULL
Internal (user) modeuser_calendar_connectionsbooking_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:

ModeState FormatExample
Client{clientId}:{provider}abc123:google
Internaluser:{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:

  1. Check the edge function logs for google-calendar-callback or outlook-calendar-callback
  2. Verify the OAuth redirect URI matches what is configured in the Google/Outlook app settings
  3. 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

ModeAvailability Source
Clientclients.availability_settings
Internaluser_profiles.availability_settings

If the edge function queries the wrong table, it will get NULL settings.

Symptoms: Visiting /book/<slug> returns a 404 or blank page.

Checklist:

  1. Verify slug exists:

    SELECT id, slug, is_active, client_id, user_id
    FROM booking_links
    WHERE slug = '<slug>';
  2. Check is_active: If false, the link is deactivated and will not resolve.

  3. Check the routing: The BookingPage.tsx component loads booking links by slug. Verify the route is correctly defined in App.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:

  1. Check edge function logs for create-calendar-event -- look for errors

  2. Verify calendar connection is active:

    -- For internal booking links
    SELECT * FROM user_calendar_connections
    WHERE user_id = '<user-id>' AND is_active = true;
  3. 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;
  4. Check notification delivery: If the meeting was created but no email was sent, check send-booking-notification edge function logs.