Skip to content
Skip to main content

Sync calendars across providers

Last updated:

Keeping events in sync across Google Calendar, Outlook, and iCloud usually means three separate integrations, each with its own OAuth project, event shape, and quirks. The Google Calendar API, Microsoft Graph, and CalDAV disagree on how they represent event IDs, recurrence rules, and change notifications. Write a sync engine against all three directly and most of your code becomes provider translation, not sync logic.

This guide shows how to sync events across providers through one schema and one webhook channel, then maps the differences that still leak through, so your sync stays correct when a recurring Outlook event and an iCloud event land in the same database table.

Which calendar API supports both Google Calendar and Outlook Calendar?

Section titled “Which calendar API supports both Google Calendar and Outlook Calendar?”

The Nylas Calendar API reads Google Calendar, Outlook, Microsoft 365, iCloud, and Exchange through one schema. You list events with GET /v3/grants/{grant_id}/events?calendar_id=<CALENDAR_ID>, and every provider returns the same JSON: a when object, participants, and a recurrence array. One grant per connected account replaces three separate API clients.

The same call works whether the account is a personal Gmail address, a Microsoft 365 mailbox behind admin consent, or an iCloud calendar authenticated with an app-specific password. Because the response shape is identical across all 4 supported calendar providers (Google, Microsoft, iCloud, and Exchange), your parser, your database schema, and your sync diff logic stay provider-agnostic. The differences that remain (covered below) are narrow and predictable, not a full per-provider rewrite. For a comparison of the unified layer against calling Microsoft directly, see Outlook vs Microsoft Graph calendar.

How do I sync calendar events across multiple providers using one API?

Section titled “How do I sync calendar events across multiple providers using one API?”

Sync through three moving parts: list events per calendar with GET /v3/grants/{grant_id}/events, subscribe to event.created, event.updated, and event.deleted webhooks through POST /v3/webhooks, and key your local records on the stable event ID. The webhook channel pushes changes from every provider into one handler, so you never poll three APIs on a timer.

A reliable sync starts with a one-time backfill, then runs incrementally off webhooks. List each calendar’s events, store them keyed by the Nylas event ID, then let webhooks drive every later change. The first call below lists events from a connected calendar. The calendar_id parameter is required; pass primary for the account’s default calendar, and use limit (max 200) to size each page. This same request returns Google, Outlook, and iCloud events in one shape.

To find every calendar on an account before you back-fill, call GET /v3/grants/{grant_id}/calendars. It returns each calendar with a read_only flag, so you can skip calendars your app can’t write back to. For the deeper two-way pattern, including conflict handling and write-back, see build a two-way calendar sync.

How do I handle calendar provider differences when syncing across Google, Outlook, and iCloud?

Section titled “How do I handle calendar provider differences when syncing across Google, Outlook, and iCloud?”

Three differences matter most: event ID stability, recurrence representation, and update detection. The API normalizes the JSON shape, but it can’t change how each provider issues IDs or expresses recurring series. Store the provider name alongside every event and branch on these three behaviors, and your cross-provider sync stays correct.

Event IDs behave differently per provider. Google keeps the same event ID for an event no matter which user queries it, so the same meeting in two attendees’ calendars shares one ID. Microsoft and iCloud issue IDs that are unique per user, so the same meeting carries different IDs in each mailbox. Treat the Nylas event ID plus the grant_id as your composite key, not the event ID alone, or you’ll collide Google events that legitimately share an ID across two grants you sync.

Recurrence is the second difference. The recurrence array holds RRULE and EXDATE strings, and it appears only on the master event, not on individual instances. Two provider-specific facts will bite you: Microsoft Graph adds one day to the UNTIL date in a rule, so a series that should end June 30 reports July 1, and iCloud won’t let you convert a recurring event to non-recurring at all. Use the master_event_id parameter to fetch a series’ instances, and store the ical_uid to correlate the same meeting across systems. Note that ical_uid can be null for some synced events, and recurring events may share one ical_uid.

How do I get real-time updates from every calendar at once?

Section titled “How do I get real-time updates from every calendar at once?”

Subscribe one webhook to the calendar triggers and every provider’s changes arrive at the same endpoint. Create a subscription with POST /v3/webhooks, set trigger_types to event.created, event.updated, and event.deleted, and point webhook_url at your handler. Nylas watches Google, Outlook, and iCloud server-side and posts a notification when any event changes, so you stop polling three APIs.

The request below registers a single subscription covering all three event triggers across every connected account. One webhook channel replaces per-resource subscriptions like Microsoft Graph’s, which expire and need renewal roughly every few days. After registration, verify the endpoint with the challenge handshake Nylas sends, then process each notification by event ID.

For the full webhook setup, including signature verification and the challenge response, see calendar webhooks. When you can’t expose a public endpoint yet, fall back to polling GET /v3/grants/{grant_id}/events with the updated_after filter to pull only events changed since your last sync run.

The table compares building cross-provider sync on the native APIs against one unified schema. Native APIs give you the deepest provider-specific control; the unified path collapses three integrations into one code path. The right choice depends on how many providers you support and how much provider-specific surface you actually touch.

TaskNative APIs (Google + Graph + CalDAV)Nylas unified API
Auth3 OAuth projects, 3 token flows1 connector per provider, one grant per account
Event schema3 different shapes to mapOne shape across all providers
RecurrencePer-provider RRULE parsingOne recurrence array, documented differences
Change notificationsGraph subscriptions renew ~every 3 daysOne webhook, no renewal
Providers reachedGoogle, Microsoft, CalDAV separatelyGoogle, Microsoft, iCloud, Exchange

Be honest about the tradeoff: if you support a single provider and need surfaces beyond calendar, like Microsoft Teams or SharePoint, call that provider’s API directly. The unified layer pays off the moment you add a second provider or only need calendar data. The official RFC 5545 iCalendar spec defines the RRULE grammar both approaches rely on.

How do I keep sync incremental and avoid duplicates?

Section titled “How do I keep sync incremental and avoid duplicates?”

Run incremental sync off the updated_after filter and dedupe on a composite key. After the initial backfill, query GET /v3/grants/{grant_id}/events with updated_after set to your last successful sync timestamp, so you fetch only changed events instead of every event each run. This keeps each sync cheap and well under the 200-event page limit for most accounts.

Deduplication is the other half. Because Google shares one event ID across attendees while Microsoft and iCloud don’t, key your records on grant_id plus event ID, never the event ID alone. Page through results with the next_cursor value from each response, passing it as page_token on the next call until the field is absent. Pair updated_after with webhook notifications: webhooks handle real-time changes, and a periodic updated_after sweep catches anything a missed or delayed webhook left behind, which is the standard safety net for production sync.