How it works
The Google Calendar API uses notification channels to deliver webhooks. The flow is:- You call the
watchendpoint for the Google Calendar you want to monitor, passing your Nango webhook URL as the channeladdress. - Google creates a notification channel and sends an initial
syncmessage to your URL. - When the watched calendar changes, Google sends a notification to Nango
- Nango matches the notification to the correct connection and forwards it to your app
X-Goog-Resource-URI header (e.g. https://www.googleapis.com/calendar/v3/calendars/user%40example.com/events?alt=json) and matches the webhook to a connection. For new connections on the google-calendar integration, Nango persists a hash of the user’s email in connection_config.emailAddressHash at connection time, so the usual primary-calendar flow in this guide needs no extra metadata for routing.
Watching additional calendars uses a different metadata-based step; see Connection matching.
Setup
1. Get your webhook URL
Copy the webhook URL from your Google Calendar integration page in the Nango dashboard, under the Webhook URL section. This is the HTTPS URL you will use as the channeladdress when creating notification channels.
2. Create a notification channel (watch a resource)
Create a notification channel for the calendar you want to watch by sending aPOST request to the appropriate watch endpoint. See the API reference for the exact URI.
You can automate creating the channel for new connections with a post-connection-creation script:
- <CALENDAR_ID> — The calendar to watch, identified by the user’s email address.
- <AUTH_TOKEN> — A valid OAuth 2.0 access token for the user who owns or has access to the calendar.
- address — Your Nango webhook URL from the dashboard.
- id — A unique string (e.g. UUID) identifying this channel; max 64 characters. It is echoed in
X-Goog-Channel-IDon every notification. - expiration — (Optional) Unix timestamp in milliseconds when the channel should stop sending notifications. If omitted, Google applies a default; channels must be renewed before they expire.
3. Renew the notification channel
Google does not renew channels automatically. When a channel is close to its expiration, you must create a new channel by calling thewatch endpoint again with a new unique id. After the new channel is created successfully, stop the old channel so only one remains active. See Renew notification channels.
You can use a Nango sync with a suitable frequency (e.g. every few days) to renew the watch before it expires:
4. Stop notifications on connection deletion
If a connection is deleted in Nango but the channel remains active, Google may continue sending notifications until the channel expires. To stop notifications immediately for a deleted connection, callchannels.stop before deletion.
You can automate this with a pre-connection-deletion lifecycle event. This uses the googleCalendarChannelId and googleCalendarResourceId stored in metadata during channel creation (step 2) and renewal (step 3):
5. Handle forwarded webhooks
When a Calendar notification arrives, Nango matches it to the correct connection and forwards it to your system. Notification messages have no body; Google sends only HTTP headers. Nango forwards those headers in the payload. Example structure:| Header | Description |
|---|---|
x-goog-resource-state | sync = channel created; exists = resource changed (create/update/delete). |
x-goog-resource-uri | The watched resource (e.g. which calendar’s events). |
x-goog-channel-id | The channel id you sent when creating the channel. |
x-goog-message-number | Incrementing message number for this channel. |
1d or 1h) as a safety net for missed notifications.
If you prefer Nango to automatically run a sync when the webhook arrives (instead of forwarding it to your app), you can enable webhook processing in a sync script using
webhookSubscriptions and onWebhook. For Google Calendar, subscribe to '*' and use the forwarded headers (e.g. x-goog-resource-state, x-goog-resource-uri) to decide what to fetch.See: Real-time syncsConnection matching
Primary calendar
For primary calendars, Nango matches the incoming request to a connection using the calendar identifier (email) extracted fromX-Goog-Resource-URI (e.g. .../calendars/user@example.com/events...). The lookup order is:
connection_config.emailAddressHash— automatically set by Nango for new connections (created after 2026-03-11)metadata.emailAddress— fallback (customer-controlled)metadata.email— fallback (customer-controlled)
- Re-authorize the connection so Nango’s post-connection hook runs and persists the email hash automatically
- Set the metadata manually via the API:
Other calendars
If you want to subscribe to notifications from other calendars besides the primary calendar, add the resource URIs for each calendar tometadata.googleCalendarWatchResourceUris:
- The value must be a JSON
string[]. Store theX-Goog-Resource-URIstring exactly as Google sends it: same encoding, path, and query string. - The lookup on
metadata.googleCalendarWatchResourceUristakes priority over the primary calendar matching described above for primary calendars, so include the URIs for all calendars you are subscribing to, including the primary calendar.
googleCalendarWatchResourceUris, every matching connection is included. Nango forwards the webhook once per matching connection, with appropriate connectionId.
X-Goog-Resource-URI values to this connection.
To do this in bulk, iterate over the list connections response and update the metadata for each one.
Rollback strategy
To stop webhooks:- Call
channels/stopfor each channel you created (using the channelidandresourceId), or - Let the channel expire by not renewing it.
id and your Nango webhook URL as address.