> ## Documentation Index
> Fetch the complete documentation index at: https://nango-wari-add-support-for-sagemember.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# How to setup webhooks with Google Drive on Nango

> Learn how to set up Google Drive webhooks with notification channels and Nango

This guide shows you how to receive real-time Google Drive webhooks in Nango using [push notifications](https://developers.google.com/workspace/drive/api/guides/push). You configure a webhook URL directly with the Drive API.

## How it works

The Google Drive API supports two kinds of notification channels:

* **`changes.watch`** — watches every change across the user's Drive (or one shared drive). This is the recommended approach.
* **`files.watch`** — watches a single file for changes. Use this when you only care about one specific file; see the note at the end of [step 2](#2-create-a-notification-channel-watch-the-drive).

The flow:

1. You call `changes.watch`, passing your Nango webhook URL as the channel `address`.
2. Google creates a notification channel and sends an initial `sync` message to your URL.
3. When anything changes in the watched Drive, Google sends a notification to Nango.
4. Nango matches the notification to the correct connection and forwards it to your app.

Drive has no built-in identifier Nango can derive automatically — you must store the channel's resource identifier yourself. See [Connection matching](#connection-matching) for how this works.

## Setup

### 1. Get your webhook URL

Copy the webhook URL from your Google Drive integration page in the Nango dashboard, under the **Webhook URL** section. This is the HTTPS URL you will use as the channel `address` when creating notification channels.

### 2. Create a notification channel (watch the Drive)

You can automate creating the channel for new connections with a [post-connection-creation script](/implementation-guides/use-cases/implement-event-handler):

```typescript theme={null}
import { createOnEvent, ProxyConfiguration } from 'nango';
import { randomUUID } from 'crypto';

export default createOnEvent({
    event: 'post-connection-creation',
    description: 'Create a Google Drive notification channel for the whole Drive',
    metadata: z.object({
        googleDriveChannelId: z.string().optional(),
        googleDriveWatchResourceIds: z.array(z.string()).optional(),
        googleDriveWatchDriveId: z.string().optional(),
    }),
    exec: async (nango) => {
        const driveId = undefined; // set to a shared drive ID to watch that drive instead of "My Drive"
        const channelId = randomUUID();
        const expiration = Date.now() + 7 * 24 * 60 * 60 * 1000; // 7 days in ms, Drive's max for changes.watch

        try {
            const webhookUrl = await nango.getWebhookURL();

            const startPageTokenResponse = await nango.get({
                endpoint: '/drive/v3/changes/startPageToken',
                ...(driveId && { params: { driveId, supportsAllDrives: 'true' } })
            });

            const config: ProxyConfiguration = {
                endpoint: '/drive/v3/changes/watch',
                params: {
                    pageToken: startPageTokenResponse.data.startPageToken,
                    ...(driveId && { driveId, supportsAllDrives: 'true', includeItemsFromAllDrives: 'true' })
                },
                data: {
                    id: channelId,
                    type: 'web_hook',
                    // Webhook URL can be found on https://app.nango.dev/dev/integrations/google-drive (your integration settings page)
                    address: webhookUrl,
                    expiration: expiration,
                },
            };

            const response = await nango.post(config);

            // Store the channel info so it can be used to stop notifications later (step 4) and to
            // route incoming notifications to this connection (see "Connection matching" below).
            // Persisting driveId ensures the renewal sync (step 3) keeps watching the same shared drive.
            await nango.updateMetadata({
                googleDriveChannelId: channelId,
                googleDriveWatchResourceIds: [response.data.resourceId],
                ...(driveId && { googleDriveWatchDriveId: driveId }),
            });
        } catch (err) {
            // Avoid blocking connection creation if channel creation fails.
            await nango.log(`Failed to create Drive notification channel: ${String(err)}`, { level: 'error' });
            return;
        }
    }
});
```

Replace:

* **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-ID` on every notification.
* **expiration** — (Optional) Unix timestamp in **milliseconds** when the channel should stop sending notifications. Drive's own maximum is 7 days for `changes.watch`; if you request longer, Google caps it. If omitted, the default is 1 hour after the current time.

See the [API reference](https://developers.google.com/workspace/drive/api/reference/rest/v3/changes/watch) for the exact request shape.

<Note>
  To watch a single file instead of the whole Drive, call `files.watch` instead: drop the `pageToken` param and `startPageToken` call, and set `endpoint: "/drive/v3/files/${fileId}/watch"` (24h max expiration instead of 7 days).
</Note>

### 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 `changes.watch` again with a **new** unique `id` and a fresh `pageToken`. After the new channel is created successfully, stop the old channel so only one remains active. See [Renew notification channels](https://developers.google.com/workspace/drive/api/guides/push#renew-notification-channels).

You can use a Nango sync with a suitable frequency (e.g. every day, given the 7-day maximum for `changes.watch`) to renew the watch before it expires:

```typescript theme={null}
import { createSync, ProxyConfiguration } from 'nango';
import { randomUUID } from 'crypto';

export default createSync({
    description: 'Renew the Google Drive changes.watch channel before it expires',
    models: {},
    metadata: z.object({
        googleDriveChannelId: z.string().optional(),
        googleDriveWatchResourceIds: z.array(z.string()).optional(),
        googleDriveWatchDriveId: z.string().optional(),
    }),
    syncType: 'full',
    frequency: '1d',
    exec: async (nango) => {
        const metadata = await nango.getMetadata();
        const oldChannelId = metadata['googleDriveChannelId'];
        const oldResourceId = metadata['googleDriveWatchResourceIds']?.[0];
        const driveId = metadata['googleDriveWatchDriveId']; // undefined when watching "My Drive"

        const channelId = randomUUID();
        const expiration = Date.now() + 7 * 24 * 60 * 60 * 1000; // 7 days in ms
        const webhookUrl = await nango.getWebhookURL();

        const startPageTokenResponse = await nango.get({
            endpoint: '/drive/v3/changes/startPageToken',
            ...(driveId && { params: { driveId, supportsAllDrives: 'true' } })
        });

        const config: ProxyConfiguration = {
            endpoint: '/drive/v3/changes/watch',
            params: {
                pageToken: startPageTokenResponse.data.startPageToken,
                ...(driveId && { driveId, supportsAllDrives: 'true', includeItemsFromAllDrives: 'true' })
            },
            data: {
                id: channelId,
                type: 'web_hook',
                address: webhookUrl,
                expiration: expiration,
            },
        };

        const response = await nango.post(config);

        // Stop the old channel only after the new one was successful
        if (response.status === 200 && oldChannelId && oldResourceId) {
            try {
                await nango.post({
                    endpoint: '/drive/v3/channels/stop',
                    data: { id: oldChannelId, resourceId: oldResourceId },
                });
            } catch (err) {
                await nango.log(`Failed to stop previous channel: ${String(err)}`, { level: 'error' });
            }
        }

        // Update stored channel info for use in stop notifications (step 4) and connection matching
        await nango.updateConnectionConfig({
            googleDriveChannelId: channelId,
            googleDriveWatchResourceIds: [response.data.resourceId],
            ...(driveId && { googleDriveWatchDriveId: driveId }),
        });
    }
});
```

### 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, call `channels.stop` before deletion.

You can automate this with a `pre-connection-deletion` lifecycle event. This uses the `googleDriveChannelId` and `googleDriveWatchResourceIds` stored in metadata during channel creation (step 2) and renewal (step 3):

```typescript theme={null}
import { createOnEvent, ProxyConfiguration } from 'nango';

export default createOnEvent({
    event: 'pre-connection-deletion',
    description: 'Stop Google Drive notification channel before connection deletion',
    exec: async (nango) => {
        const metadata = await nango.getMetadata();
        const channelId = metadata['googleDriveChannelId'];
        const resourceId = metadata['googleDriveWatchResourceIds']?.[0];

        if (!channelId || !resourceId) {
            return;
        }

        const config: ProxyConfiguration = {
            endpoint: '/drive/v3/channels/stop',
            data: {
                id: channelId,
                resourceId
            }
        };

        try {
            await nango.post(config);
        } catch (err) {
            // Avoid blocking connection deletion if stop fails.
            await nango.log(`Failed to stop Drive channel: ${String(err)}`, { level: 'error' });
        }
    }
});
```

### 5. Handle forwarded webhooks

When a Drive 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:

```json theme={null}
{
  "from": "google-drive",
  "providerConfigKey": "google-drive",
  "type": "forward",
  "payload": {
    "x-goog-channel-id": "01234567-89ab-cdef-0123456789ab",
    "x-goog-resource-id": "ret08u3rv24htgh289g",
    "x-goog-resource-uri": "https://www.googleapis.com/drive/v3/changes",
    "x-goog-resource-state": "change",
    "x-goog-changed": "content",
    "x-goog-message-number": "10"
  },
  "connectionId": "connection-123"
}
```

Relevant headers (see [Google's documentation](https://developers.google.com/workspace/drive/api/guides/push#interpret-the-notification-message-format)):

| Header                  | Description                                                                                                                                                   |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `x-goog-resource-state` | `sync` = channel created; `change` for `changes.watch`.                                                                                                       |
| `x-goog-resource-uri`   | The watched resource. Identical across every connection watching the same target (e.g. every "My Drive" watch), so it can't be used to identify a connection. |
| `x-goog-resource-id`    | Stable identifier for the watched resource, unique per watched Drive/shared drive. Nango matches on this — see [Connection matching](#connection-matching).   |
| `x-goog-channel-id`     | The channel `id` you sent when creating the channel.                                                                                                          |
| `x-goog-message-number` | Incrementing message number for this channel.                                                                                                                 |

Notifications do **not** include the changed files themselves — call `changes.list` with a stored `pageToken` to fetch what changed since your last check, then save the `newStartPageToken` it returns for the next call. After receiving the webhook, trigger your sync or API calls for that connection:

```bash theme={null}
curl -X POST "https://api.nango.dev/sync/trigger" \
  -H "Authorization: Bearer <NANGO_SECRET_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "sync_mode": "incremental",
    "connection_id": "<CONNECTION_ID>",
    "provider_config_key": "google-drive",
    "syncs": ["documents"]
  }'
```

With webhooks driving real-time updates, you can run syncs less often (e.g. `1d` or `1h`) as a safety net for missed notifications.

<Note>
  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 Drive, 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 syncs](/implementation-guides/use-cases/syncs/realtime-syncs)
</Note>

## Connection matching

Google Drive has no built-in identifier Nango can derive automatically, so you must always register the watched resource yourself. Nango matches incoming Drive notifications by resource ID, against **`metadata.googleDriveWatchResourceIds`** — the value you store via `nango.updateMetadata()` in your post-connection-creation script (step 2) and renewal sync (step 3).

* The value must be a JSON **`string[]`**. Store the **`X-Goog-Resource-ID` string exactly as Google sends it**.
* Update metadata from your own Nango functions via `nango.updateMetadata()`, or manually via the [update metadata API](/reference/api/connection/update-metadata).

If you use the post-connection-creation script from [step 2](#2-create-a-notification-channel-watch-the-drive), this is already handled for you.

**Duplicates:** If more than one connection stores the **same** value in `googleDriveWatchResourceIds` (e.g. two connections watching the same shared drive), **every** matching connection is included. Nango forwards the webhook **once per matching connection**, with the appropriate `connectionId`.

<Warning>
  If Nango cannot match the incoming webhook to a connection, the webhook will still be forwarded but won't include a `connectionId` in the payload.
</Warning>

## Rollback strategy

To stop webhooks:

1. Call `channels/stop` for each channel you created (using the channel `id` and `resourceId`), or
2. Let the channel expire by not renewing it.

Re-enable notifications by creating a new channel with a new `id` and your Nango webhook URL as `address`.

<Tip>Need help getting started? Get help in the [community](https://nango.dev/slack).</Tip>

***
