v1.0 Dashboard Public Profile GitHub
Deferred — Needs Staging OAuth2 + Guzzle REST

Google Calendar Integration

KaiVox syncs appointments to Google Calendar automatically — one-way (KaiVox → Google). The integration uses OAuth2 and pure Guzzle REST calls (no google/apiclient package). It cannot be tested on localhost due to Google's domain restrictions.

Cannot test locally. Google Cloud Console rejects http://kaivox-ai.test as an OAuth redirect URI. It requires a valid public top-level domain (.com, .org, etc.). Testing must happen on kaivox-test.omajestic.com or kaivoxai.com.

Current State

ItemStatus
OAuth2 connect/disconnect UI✅ Built — at Settings → Integrations tab
Auto-sync on appointment create✅ Built — wired in AppointmentsController
Auto-sync on appointment update✅ Built
Event delete on cancellation✅ Built
Token refresh logic✅ Built — expiry detection + refresh call
Testing on localhost❌ Blocked — Google rejects .test domain
Two-way sync (Google → KaiVox)❌ Deferred — needs push notifications/webhooks
Outlook/Microsoft Calendar❌ Deferred
When GOOGLE_CLIENT_ID is not set, the integration tab shows: "Google Calendar sync is being set up. It will be available soon." — tenants see no broken state.

Environment Variables

KeyValueRequired
GOOGLE_CLIENT_IDFrom Google Cloud Console → OAuth 2.0 Clients✅ Yes (leave blank until staging)
GOOGLE_CLIENT_SECRETFrom Google Cloud Console → OAuth 2.0 Clients✅ Yes (leave blank until staging)
GOOGLE_REDIRECT_URIFull callback URL for your environment✅ Yes
.env — staging
GOOGLE_CLIENT_ID=123456789-xxxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxxxxx
GOOGLE_REDIRECT_URI=https://kaivox-test.omajestic.com/settings/google-calendar/callback
.env — production
GOOGLE_CLIENT_ID=123456789-xxxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxxxxx
GOOGLE_REDIRECT_URI=https://kaivoxai.com/settings/google-calendar/callback

Setup Checklist (Do Once on Staging)

  1. Create a Google Cloud project Go to console.cloud.google.com → New Project → name it "KaiVox AI".
  2. Enable Google Calendar API APIs & Services → Library → search "Google Calendar API" → Enable.
  3. Create OAuth 2.0 credentials APIs & Services → Credentials → Create Credentials → OAuth client ID.
    • Application type: Web application
    • Name: KaiVox AI
    • Authorized redirect URIs: Add both staging and production URIs (see above)
  4. Configure OAuth consent screen APIs & Services → OAuth consent screen.
    • User type: External (for multi-tenant use)
    • App name: KaiVox AI
    • Scopes: https://www.googleapis.com/auth/calendar
    • Add test users: your own Google account for testing
    Note: For production, you must submit for Google verification if accessing calendar data for non-test users.
  5. Add credentials to staging .env Copy the Client ID and Client Secret into .env on the staging server. Set GOOGLE_REDIRECT_URI to the staging callback URL.
  6. Test the OAuth flow Log in as a tenant on staging → Settings → Integrations → Connect Google Calendar → complete Google consent screen → should return to Settings with a success message.
  7. Verify sync works Create an appointment → check that an event appears in the connected Google Calendar. Cancel the appointment → event should be deleted from Google Calendar.

How the OAuth Flow Works (Code)

Routes

GET  /settings/google-calendar/connect    → redirect to Google consent screen
GET  /settings/google-calendar/callback   → exchange code for tokens, store in tenant
GET  /settings/google-calendar/disconnect → revoke tokens, clear from tenant

Token Storage

OAuth tokens are stored in the tenants table:

tenants.google_access_token   — short-lived access token
tenants.google_refresh_token  — long-lived refresh token
tenants.google_token_expires_at — expiry timestamp

Sync Logic (GoogleCalendarService)

// Called from AppointmentsController on create/update:
GoogleCalendarService::syncAppointment($appointment);

// Called on cancel:
GoogleCalendarService::deleteEvent($appointment);

// Token refresh (automatic, checked before every API call):
if (now() >= $tenant->google_token_expires_at) {
    GoogleCalendarService::refreshToken($tenant);
}

What Gets Synced

KaiVox DataGoogle Calendar Field
Appointment date + timeEvent start/end
Customer name + phoneEvent title: "Appointment — [Customer Name]"
Service nameEvent description
Staff memberOrganiser / attendee
Appointment notesEvent description (appended)
Status = cancelledEvent deleted from Google Calendar

Two-Way Sync (Deferred)

Currently, sync is one-way only: KaiVox → Google. Changes made directly in Google Calendar are not reflected in KaiVox. Two-way sync requires Google Calendar push notifications (webhooks from Google to KaiVox) — this is deferred to a future phase.