TotalApp Docs

Calendar Sync

Connect Google Calendar for on-demand two-way synchronisation — confirmed and pending bookings push out to your Google Calendar, and existing external events are reported back as a count of busy slots. Microsoft Outlook support is coming soon.

Overview

Calendar Sync connects your Google account so confirmed and pending TotalApp bookings can be pushed to your Google Calendar, and existing events on that calendar are reported back as a busy-slot count. Sync runs on demand — there is no background job — every time you click Sync Now, TotalApp requests a fresh Google access token and calls the real Google Calendar API v3.

Google Calendar

Connect via a Google Identity Services consent popup (not a full-page OAuth redirect). Grants a short-lived calendar.events-scoped token, requested fresh on each Connect/Sync click.

Microsoft Outlook

Coming soon. The Outlook card is shown for reference but Connect is disabled — Microsoft Graph sync is not implemented yet.

Outgoing + Incoming

Outgoing sync creates or updates one Google Calendar event per confirmed/pending booking. Incoming sync counts upcoming events on the calendar that TotalApp did not create. Each direction has its own toggle.

Sync Log

A rolling, in-memory log of recent sync operations shows success, error, and direction (outgoing / incoming) for every sync you trigger.

Connecting Google Calendar

  1. Navigate to Calendar Sync in the sidebar.
  2. On the Google Calendar card, click Connect Google Calendar.
  3. A real Google consent popup opens (Google Identity Services), requesting the https://www.googleapis.com/auth/calendar.events scope. This is a genuine Google OAuth prompt, not a TotalApp-drawn modal.
  4. After you approve, TotalApp receives a short-lived access token and immediately verifies it with a zero-op call to /api/google-calendar/sync before marking the account as Connected.
  5. The provider card updates to show the sync toggle controls and the last sync timestamp. The primary calendar is used — there is no calendar picker yet.

Token is not stored — you may see the popup again

Only a connected: true flag and your sync preferences are saved server-side (in appointments.json, via saveSyncToken). The actual Google access token is never persisted — it lives only in the browser for the duration of one Connect or Sync Now action. Each click requests a fresh token; Google will silently re-grant it without a popup if you're still within the same browser session, and will show the consent screen again once the token has expired or the session is gone.

Sync Directions

Outgoing Sync (TotalApp → Google Calendar)

When you click Sync Now with Outgoing Sync enabled, TotalApp fetches all bookings with status confirmed or pending and sends them to /api/google-calendar/sync. For each booking, the server looks for an existing event carrying that booking's ID in extendedProperties.private.totalappBookingId: if found, the event is patched (title, start/end time, notes); if not found, a new event is created. This makes repeated syncs idempotent — re-running Sync Now updates existing events rather than duplicating them.

Booking FieldGoogle Calendar Event Field
customerNamesummary ("<name> — TotalApp booking")
notesdescription
date + startTime / endTimestart.dateTime / end.dateTime
idextendedProperties.private.totalappBookingId (used to find the event on the next sync)

Cancelling or deleting a booking in TotalApp does not currently update or remove the corresponding Google Calendar event — that event is left as-is until you clean it up manually.

Incoming Sync (Google Calendar → TotalApp)

When Incoming Sync is enabled, the server lists up to 50 upcoming events on the connected calendar and counts how many do not carry a totalappBookingId (i.e. were not created by TotalApp's outgoing sync). That count is shown in the Sync Log — for example "Imported 3 external event(s) as blocked slots". No booking records or blocked-slot entries are actually created in TotalApp from this count today; the availability engine does not yet read from Google Calendar, so external events do not block the public booking page. This is a reporting-only view of how many external events exist, not a working two-way block.

Sync Frequency

There is no background job, cron, or webhook. Sync only runs when a signed-in user clicks Sync Now on a connected provider card — everything in this document happens synchronously in that one request.

Sync Toggles

Each connected provider card shows two independent toggles:

ToggleOnOff
Outgoing SyncConfirmed/pending bookings are pushed to Google Calendar on the next Sync Now clickSync Now skips the outgoing step entirely
Incoming SyncSync Now also counts external events on the calendarSync Now skips the incoming step entirely

Toggle changes are saved immediately via updateSyncToken — there is no Save button.

Sync Log

The Sync Log section at the bottom of the page shows the most recent sync operations in reverse chronological order. Each entry includes:

  • Status icon — green check (success) or red triangle (error, e.g. the Google token request failed or expired).
  • Message — how many bookings were pushed, or how many external events were counted.
  • Direction badgeoutgoing (blue) or incoming (amber).
  • Provider — currently always google.
  • Timestamp — local time of the sync event.

The log is kept in component state only (up to 50 entries) and resets on page reload — it is not persisted to the server.

Disconnecting Google Calendar

  1. Open the Google Calendar provider card.
  2. Click the Disconnect button (unlink icon, red outline hover).
  3. The sync preference record is deleted from appointments.json and the provider card reverts to the unconnected state.
  4. Events already created in Google Calendar by outgoing sync are not cleaned up automatically — delete them manually in Google Calendar if needed.

Revoke access at Google too

Disconnecting in TotalApp only removes the local sync preference — since no refresh token is stored server-side, there is nothing to revoke on TotalApp's end. If you want to fully revoke the granted calendar.events scope, visit your Google Account's Third-party apps & services page.

API Endpoints Reference

MethodEndpointPurpose
POST/api/google-calendar/syncAuthenticated (app JWT required). Accepts { accessToken, calendarId, syncOutgoing, syncIncoming, bookings }, calls Google Calendar API v3 directly with the caller-supplied access token, and returns { outgoingCount, incomingCount }. Used both to verify a new connection (empty booking list, both directions off) and to perform a real Sync Now.
GET/POST/api/data/appointmentsGeneric tenant-scoped JSON store — holds resources, bookings, and syncTokens (the connection flag + preferences, no OAuth tokens).

There is no separate OAuth authorize/callback route — the Google token is obtained entirely client-side via Google Identity Services (window.google.accounts.oauth2.initTokenClient) and forwarded to the server per-request, the same pattern already used by the Google Sheets and Google Drive integrations elsewhere in TotalApp.

Security Considerations

  • The Google access token is never written to disk or to the appointments JSON store — it only exists in browser memory for the duration of a single Connect or Sync Now click, then is discarded.
  • The /api/google-calendar/sync endpoint requires a valid TotalApp app JWT (requireAppAuth) — an attacker without a signed-in session cannot call it, even if they somehow obtained a Google access token.
  • The requested scope is deliberately narrow: https://www.googleapis.com/auth/calendar.events only grants read/write access to events, not full calendar management (sharing, deleting whole calendars, etc.).
  • Because no refresh token is requested or stored, there is no long-lived credential to protect at rest — the tradeoff is that background/offline sync is not possible with the current design.

Frequently Asked Questions

Does Calendar Sync run automatically in the background?
No. There is no cron job, polling interval, or webhook. Every sync — outgoing, incoming, or both — only happens when a user clicks Sync Now while signed in.
What happens if a sync fails?
A red error entry is added to the Sync Log (typically because the Google token request failed, was denied, or expired). There is no automatic retry — click Sync Now again to retry manually.
Will existing TotalApp bookings be synced when I connect for the first time?
No — connecting only verifies access and saves your sync preferences. Click Sync Now afterwards to push existing confirmed/pending bookings to Google Calendar.
Does incoming sync actually block time slots on the public booking page?
Not yet. Incoming sync currently only reports a count of external events found on your Google Calendar in the Sync Log — it does not create blocked-slot bookings or affect availability shown to customers.
When is Outlook / Microsoft 365 support coming?
The Outlook card is visible today but disabled ("Coming soon"). It's on the roadmap — implementing it will follow the same pattern as Google (a Microsoft Graph consent popup plus a server endpoint that calls the Graph API directly).
Can I sync with Apple Calendar (iCloud)?
Not currently. Apple Calendar uses CalDAV rather than a REST API, which would need a different integration approach.