Skip to content

Google Calendar ​

Two-way sync between your Kinderpedia activities calendar and your Google Calendar. Each staff member connects their own account — there's no school-level Google Calendar.

Connecting ​

Integrations → Google Calendar → Connect opens Google's OAuth consent. Scopes requested:

  • calendar.events — create / read / update events on your calendars
  • userinfo.email — bind the connection to your Google identity
  • userinfo.profile — display name in the tile

After consent, Google hands back an access token + refresh token + your primary email. Stored encrypted on your personal integration record — one per user per school, so the calendar overlay shows your events only.

The tile flips green. From now on, the activities calendar (/activities-calendar) shows a toggle to overlay your Google events alongside your Kinderpedia activities.

Read overlay (Google → app) ​

When the toggle is on, the calendar fetches your Google events for the same date range and renders them in your chosen overlay colour (default #4285F4 Google blue).

You can customise the overlay colour in Integrations → Google Calendar → Configure — your choice is saved per user.

Push (app → Google) ​

Three ways to push a Kinderpedia activity to your Google Calendar:

Manual per-activity ​

On any activity detail panel, click Send to Google. We POST events.insert with the activity's title, description, time, and (if a meeting URL is set) conferenceData.entryPoints[]. Google returns the new event ID — we link it to the activity so subsequent edits/deletes stay in sync.

If you have multiple calendars, a KpSelect appears so you can pick which calendar to push to.

Auto-push ​

Set Auto-push new activities in the integration config. From then on, every new activity you create is auto-pushed in the background. Useful for staff who live in Google Calendar and only check Kinderpedia for the lead context.

This only auto-pushes activities you create. Other users' activities are not touched.

Bulk push ​

Bulk push walks the next 90 days of your unlinked, upcoming activities and pushes them in one batch (capped at 100 per call). Useful for first-time setup after a long manual-only period.

Watch / two-way sync (Google → app changes) ​

When you connect, we register a watch channel with Google via events.watch:

POST /calendars/{calId}/events/watch
{
  "id": "<channelId>",
  "type": "web_hook",
  "address": "https://<your-domain>/api/integrations/google-calendar/webhook",
  "token": "<hmac>",
  "expiration": <ms_since_epoch>
}

When events on your Google calendar change, Google sends a push to /api/integrations/google-calendar/webhook on your Kinderpedia domain. Our handler:

  1. Validates X-Goog-Channel-Token HMAC (constant-time compare).
  2. Skips X-Goog-Resource-State == sync (Google's initial ack — no events to fetch).
  3. Calls events.list with the stored sync_token to incrementally fetch changes since last poll.
  4. Reconciles events carrying our extendedProperties.private.kp_activity_id marker into the corresponding lead_activities rows.

Critical caveat: native Google events (ones we didn't push) are NEVER pulled into Kinderpedia. Only events tagged with the kp_activity_id marker round-trip. This is deliberate — we don't want random work-meetings cluttering up the lead activities view.

If a Google event we pushed is deleted on Google, we clear the link locally (google_event_id = null) but keep the Kinderpedia activity intact. Use this as a soft delete: cancel on Google, the activity remains for record-keeping.

Watch lifecycle ​

Watch channels have a max TTL of 7 days. The app:google-calendar:renew-watches cron (every 6h) renews any channel expiring in <24h. On disconnect, we POST /channels/stop to release the channel cleanly.

If renewal fails (revoked refresh token, calendar deleted, …), the user_integration goes error with last_error = 'watch_renewal_failed'. The user sees a red tile on next visit; re-connecting fixes it.

Conflict warnings ​

Drag an activity onto a slot that's already occupied by a Google event → KpToast.warning("This conflicts with: <event title>"). Non-blocking — you can still save, but the warning surfaces the collision so you can re-pick.

If an activity has meeting_provider = google_meet, the push call sets conferenceData.createRequest. Google mints a fresh Meet link and we capture hangoutLink back onto the activity. See Online meetings on activities.

Multi-calendar support ​

Google users often have multiple calendars (personal, work, school-specific). The integration config lets you pick a default calendar for auto-push, and per-activity the picker overrides this.

We don't impose a "primary calendar" — every push you do can target a different one if you want.

Token refresh ​

Google access tokens are short (1h). GoogleCalendarTokenManager::getAccessToken() runs on every API call with a 60s safety window — under, we refresh. On refresh failure, the integration goes error and the next Read call returns empty (no infinite retry loops).

What data Google sees ​

Google sees the event's title, description, location, time, recurrence, attendees (if you add them), and the conferenceData block. They do NOT see your linked-lead PII unless you embed it in the description. We don't auto-embed PII — only the activity's own title/description.

Common errors ​

SymptomLikely cause
Connect fails with "redirect_uri_mismatch"Google OAuth redirect URI not registered on the Google Cloud project — contact your Kinderpedia admin.
Webhook events never arriveYour Kinderpedia webhook URL isn't publicly reachable, or watch creation was skipped on connect.
Push returns "invalid_grant"Refresh token revoked (user changed Google password, or 6-month inactivity). Re-connect.
Multi-calendar dropdown emptyGoogle API quota hit, or calendars.list returned only the primary. Wait + re-try.
Activities show on app but not on GoogleAuto-push not enabled, or your account is in error state. Check the tile.

Kinderpedia Admissions Help Center