Connect Zoom with Google Calendar
Implementation Guide
Overview: Connecting Zoom and Google Calendar
The scheduling layer of a modern B2B organisation runs on Google Calendar, but the meeting infrastructure runs on Zoom. These two systems have an overlapping but not identical model of a "meeting": Google Calendar holds the canonical scheduling record — the invited participants, the time, the location field, and the description — while Zoom holds the meeting configuration record — the join URL, the passcode, the host key, the recording settings, and the participant telemetry. When these two records are not synchronised, the result is calendar invites without join links, Zoom meetings without context, and scheduling changes in one system that leave the other in a stale state.
The native Zoom for Google Calendar add-on handles the most basic case: inserting a Zoom join URL into a Google Calendar event at creation time. But it does not handle the full spectrum of synchronisation scenarios that enterprise teams encounter: creating a Google Calendar event automatically when a Zoom meeting is programmatically created via the Zoom API (common in scheduling automation workflows), updating the calendar event when a Zoom meeting's time is changed from the Zoom portal, writing recording links back to the calendar event after a meeting ends, or creating calendar events from Zoom webinar registrations. A custom integration covering these scenarios requires direct engagement with both the Zoom API and the Google Calendar API.
Core Prerequisites
Zoom API Requirements: Access the Zoom Marketplace at marketplace.zoom.us and create a new app. For server-to-server integrations (no user login required), create a Server-to-Server OAuth app — this is Zoom's replacement for the deprecated JWT app type as of June 2023. Any integration still using Zoom JWT tokens will begin receiving 401 Unauthorized errors now that the JWT app type has been deactivated. In the Server-to-Server OAuth app configuration, navigate to Scopes and add the following: meeting:read:admin, meeting:write:admin, recording:read:admin, and user:read:admin. The :admin scope suffix indicates that the integration can act across all users in the account, not just the authenticating user. This requires the Zoom account to be a Pro, Business, or Enterprise plan. Note the Account ID, Client ID, and Client Secret from the app credentials panel.
To obtain an access token, POST to Zoom's OAuth token endpoint using HTTP Basic Authentication with the Client ID and Client Secret: POST https://zoom.us/oauth/token?grant_type=account_credentials&account_id={ACCOUNT_ID} Authorization: Basic {BASE64(CLIENT_ID:CLIENT_SECRET)}
This returns an access token valid for 3600 seconds. Implement token caching and refresh as described in the Typeform-to-HubSpot guide.
Google Calendar API / Google Cloud Requirements: Navigate to the Google Cloud Console (console.cloud.google.com), create a new project, and enable the Google Calendar API under APIs & Services > Library. Create credentials: for a service account (server-to-server, acting on behalf of a specific calendar), create a Service Account under APIs & Services > Credentials, download the JSON key file, and grant the service account domain-wide delegation in your Google Workspace admin panel under Security > API Controls > Domain-wide Delegation. Add the OAuth scope https://www.googleapis.com/auth/calendar to the service account's delegation record. This allows the service account to impersonate any user in the Google Workspace domain and manage their calendars — it requires Super Admin access in Google Workspace to configure. If domain-wide delegation is not available (e.g., you are not a Google Workspace admin), you must use the OAuth 2.0 user consent flow, which requires the calendar owner to authenticate and grant the https://www.googleapis.com/auth/calendar.events scope.
Top Enterprise Use Cases
Automated Calendar Event Creation for Programmatic Zoom Meetings: Sales teams using scheduling tools (Calendly, Chili Piper, or custom-built schedulers) that create Zoom meetings via API need those meetings to appear on the host's Google Calendar. The Zoom meeting.created webhook event triggers a Google Calendar event creation with the Zoom join URL populated in the location field and description.
Post-Meeting Recording Link Distribution: When a Zoom cloud recording becomes available (triggered by the recording.completed webhook event), the integration retrieves the recording share URL from the Zoom API and appends it to the description of the corresponding Google Calendar event, making the recording discoverable directly from the calendar entry without requiring attendees to navigate to the Zoom portal.
Meeting Cancellation Synchronisation: When a Zoom meeting is deleted from the Zoom portal, the corresponding Google Calendar event is automatically deleted or updated to remove the Zoom join link, preventing attendees from attempting to join a meeting that no longer exists.
Webinar-to-Calendar Registration: When a user registers for a Zoom webinar, a Google Calendar event is created on their calendar with the webinar join link, ensuring the event appears in their personal calendar view without requiring them to manually add it.
Step-by-Step Implementation Guide
Configuring Zoom Webhooks
In your Zoom Server-to-Server OAuth app (or a separate Zoom Webhook-only app for event subscriptions), navigate to the Event Subscriptions section. Add a new event subscription, specify your receiver's endpoint URL, and subscribe to the following events: meeting.created, meeting.updated, meeting.deleted, and recording.completed. Zoom will send a validation request to your endpoint containing a plainToken in the request body; your endpoint must respond with a JSON body containing encryptedToken, the value of plainToken signed with your Webhook Secret Token using HMAC-SHA256 and encoded as a hex string. This is different from the Asana and Graph patterns — the validation response is a JSON body, not a header echo.
A meeting.created event payload contains the following relevant fields:
{
"event": "meeting.created",
"payload": {
"account_id": "AAAABBBBCCCC",
"operator": "[email protected]",
"operator_id": "HOSTID123",
"object": {
"id": "123456789",
"uuid": "abcd1234EFGH5678==",
"host_id": "HOSTID123",
"topic": "Q3 Pipeline Review",
"type": 2,
"start_time": "2025-06-15T15:00:00Z",
"duration": 60,
"timezone": "Europe/London",
"join_url": "https://zoom.us/j/123456789?pwd=XXXXXX",
"password": "abc123"
}
}
}
The type field uses Zoom's meeting type enumeration: 1 = Instant meeting, 2 = Scheduled meeting, 3 = Recurring meeting with no fixed time, 8 = Recurring meeting with fixed time. For calendar sync purposes, you primarily care about types 2 and 8.
Creating a Google Calendar Event
Using the host's email address from the Zoom payload (operator field), use a service account with domain-wide delegation to impersonate that user and create a calendar event on their primary calendar:
POST https://www.googleapis.com/calendar/v3/calendars/primary/events
Authorization: Bearer {SERVICE_ACCOUNT_IMPERSONATION_TOKEN}
Content-Type: application/json
{
"summary": "Q3 Pipeline Review",
"location": "https://zoom.us/j/123456789?pwd=XXXXXX",
"description": "Zoom Meeting ID: 123456789\nPasscode: abc123\nJoin URL: https://zoom.us/j/123456789?pwd=XXXXXX",
"start": {
"dateTime": "2025-06-15T15:00:00Z",
"timeZone": "Europe/London"
},
"end": {
"dateTime": "2025-06-15T16:00:00Z",
"timeZone": "Europe/London"
},
"reminders": {
"useDefault": false,
"overrides": [
{ "method": "popup", "minutes": 10 }
]
},
"extendedProperties": {
"private": {
"zoom_meeting_id": "123456789",
"zoom_meeting_uuid": "abcd1234EFGH5678=="
}
}
}
Storing the Zoom meeting_id and uuid in the Google Calendar event's extendedProperties.private object is essential for the update and delete synchronisation flows. When a meeting.updated or meeting.deleted event arrives, you need to find the corresponding Google Calendar event — since there is no shared primary key between the two systems, you store the Zoom ID on the Calendar event at creation time and query for it on subsequent operations using GET /calendars/primary/events?privateExtendedProperty=zoom_meeting_id%3D123456789.
Handling Recording Completed Events
The recording.completed Zoom webhook event contains a recording_files array with objects for each recording file (shared screen, gallery view, audio-only, transcript). Each file object includes a play_url and a download_url. To append the recording to the calendar event, first query Google Calendar for the event using the stored Zoom meeting UUID, then issue a PATCH request to update only the description field:
PATCH https://www.googleapis.com/calendar/v3/calendars/primary/events/{EVENT_ID}
Authorization: Bearer {TOKEN}
Content-Type: application/json
{
"description": "Zoom Meeting ID: 123456789\n\n📹 Recording available: https://zoom.us/rec/play/XXXXXX"
}
Note that PATCH on the Calendar API performs a partial update — only the fields specified in the request body are modified. Using PUT would overwrite the entire event resource, which would erase attendee lists and other fields you do not include in the body.
Common Pitfalls & Troubleshooting
Zoom 401 Unauthorized — JWT App Deprecation: If your implementation is based on Zoom's deprecated JWT app type, all API calls now return 401 Unauthorized with error code 124 ("Invalid access token"). Migrate to Server-to-Server OAuth. The migration involves creating a new app type in the Zoom Marketplace, updating your token acquisition logic to use the account_credentials grant type, and replacing the JWT token generation logic with the OAuth token fetch and cache pattern.
Google Calendar 403 Forbidden — Domain-Wide Delegation Not Configured: If service account calls return 403 Forbidden with error.message: "Request had insufficient authentication scopes" or "Not Authorized to access this resource/api", the service account does not have domain-wide delegation, or the delegation record in Google Workspace Admin does not include the https://www.googleapis.com/auth/calendar scope. Confirm in the Google Workspace Admin Console under Security > API Controls > Manage Domain Wide Delegation that the service account's Client ID is listed with the correct scope string.
Calendar Event Not Found on Update (404): If the Zoom meeting update event arrives before the corresponding calendar event creation has completed (possible in high-concurrency scenarios), the lookup by zoom_meeting_id extended property will return no results. Implement a retry with exponential backoff for update and delete operations, allowing up to 30 seconds for the creation to complete before failing the event as unprocessable.
Zoom Webhook Duplicate Delivery: Zoom guarantees at-least-once delivery. For meeting.created events, a duplicate delivery can create duplicate Google Calendar events. Use the Zoom uuid field as an idempotency key — store created UUIDs in a cache with a 24-hour TTL and skip processing if the UUID has already been seen.
Timezone Mismatches: Zoom stores meeting times in UTC internally, but the timezone field in the payload contains the host's configured timezone string (e.g., Europe/London, America/New_York). Google Calendar requires an RFC 3339 dateTime with timezone offset. Convert Zoom's UTC timestamp using the meeting's timezone field — do not assume the host's timezone matches the server's system timezone.