feat(webhooks): add support for webhooks from multiple calendars#5787
feat(webhooks): add support for webhooks from multiple calendars#5787
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
…oks-from-multiple-calendars
|
|
||
| ### Multiple calendars | ||
|
|
||
| If you want to subscribe to notifications from multiple calendars, add the resource URIs for each calendar to **`metadata.googleCalendarWatchResourceUris`**: |
There was a problem hiding this comment.
Multiple calendars or any other calendar that isn't the primary, right?
There was a problem hiding this comment.
Are we able to know which calendars we have access at the connection post script event? Would we be able to resolve them to store them in the connection config as well? (OOS for this PR, but its something to consider when thinking about the webhooks refactor)
There was a problem hiding this comment.
Multiple calendars or any other calendar that isn't the primary, right?
No, it's exclusive. You either match by the listed googleCalendarWatchResourceUris, or we will follow the previous path and assume you want the primary calendar. It's possible for Contio's use case that someone doesn't actually want to subscribe to their primary calendar, so you must explicitly list the URI for each calendar you want to do the routing for.
Are we able to know which calendars we have access at the connection post script event? Would we be able to resolve them to store them in the connection config as well? (OOS for this PR, but its something to consider when thinking about the webhooks refactor)
So the google-calendar connection has access to all the calendars after connection, but we don't know which ones they want to subscribe to notifications for. Specifically for Contio, the user selects this in their application.
- User connects to google-calendar or google
- Contio fetches a list of all calendars
- User selects which calendars from lists they want to sync
So blindly adding all calendars I think is the wrong call and just adds noise, but happy to discuss more.
| ``` | ||
| This will associate both listed calendar resource URIs with the connection. Nango will match any incoming webhook with those exact `X-Goog-Resource-URI` values to this connection. | ||
|
|
||
| To do this in bulk, iterate over the [list connections](/reference/api/connection/list) response and [update the metadata](/reference/api/connection/update-metadata) for each one. |
There was a problem hiding this comment.
Not sure about this line, it really depends on use-case.
There was a problem hiding this comment.
I'm not following. What do you mean?
| _No pre-built syncs or actions available yet._ | ||
|
|
||
| <Tip>Not seeing the integration you need? [Build your own](/guides/primitives/functions) independently.</Tip> | ||
| <Tip>Not seeing the integration you need? [Build your own](/guides/primitives/functions) independently.</Tip> No newline at end of file |
There was a problem hiding this comment.
Maybe I'm blind, but whats the change here? Also, does something change for salesforce-cc ?
There was a problem hiding this comment.
I have no idea. I'm not super familiar with how these auto generated snippets are created. I assumed it was best practice to just commit them, but maybe not?
What should I be doing in these cases?
| await GoogleCalendarWebhookRouting.default(nangoMock as unknown as InternalNango, headers as any, {}, ''); | ||
|
|
||
| expect(mock).toHaveBeenCalledTimes(1); | ||
| expect(mock).toHaveBeenCalledWith( |
There was a problem hiding this comment.
Instead of just matching whether a mock has been called deep inside with an argument, how feasible is it to set up a test that actually has multiple connections with metadata and see what the result of the routing is?
There was a problem hiding this comment.
Not sure, let me take a look. I was just following the established pattern of unit tests for simplicity. Is this a blocker?
There was a problem hiding this comment.
Nope, I’ll approve to unblock since it’s following an existing pattern but IMO it would be a worthy addition :)
| response_path: items | ||
| docs: https://nango.dev/docs/api-integrations/google | ||
| setup_guide_url: https://nango.dev/docs/api-integrations/google/how-to-register-your-own-google-api-oauth-app | ||
| webhook_routing_script: googleWebhookRouting |
There was a problem hiding this comment.
We marked other google apis as now supporting webhooks (google-ads, google-analytics, google-bigquery, google-chat, etc) but we haven't added the routing for them? Do webhooks for those only work when connected through Google Platform? Or the docs update is wrong?
There was a problem hiding this comment.
Oh, sorry, I didn't take a close look at the auto generated updates. It will only work for google or google-calendar. How do I prevent the auto generated updates?
Co-authored-by: Agustin Ayerza <agusayerza@gmail.com>
There was a problem hiding this comment.
Solid multi-calendar webhook enhancement, but docs consistency and one missing edge-case test should be addressed before merge.
Status: Changes Suggested | Risk: Medium
Issues Identified & Suggestions
- Align URI encoding in docs example with actual Google header format:
docs/api-integrations/google-calendar/webhooks.mdx - Add test for missing x-goog-resource-uri fallback behavior:
packages/server/lib/webhook/google-calendar-webhook-routing.unit.test.ts
Review Details
📁 28 files reviewed | 💬 2 comments
Instruction Files
└── .claude/
├── agents/
│ └── nango-docs-migrator.md
└── skills
👍 / 👎 individual comments to help improve reviews for you
| } | ||
| }' | ||
| ``` | ||
| This will associate both listed calendar resource URIs with the connection. Nango will match any incoming webhook with those exact `X-Goog-Resource-URI` values to this connection. |
There was a problem hiding this comment.
[Documentation] The docs example sets googleCalendarWatchResourceUris to URIs like https://www.googleapis.com/calendar/v3/calendars/cal1@example.com/events?alt=json (unencoded @). However, the docs earlier state "Store the X-Goog-Resource-URI string exactly as Google sends it: same encoding, path, and query string," and the test fixture uses user%40example.com (percent-encoded). The example curl payload uses unencoded @ in the email addresses, which would fail to match Google's actual X-Goog-Resource-URI header (which uses %40). Update the example to use percent-encoded email addresses consistent with the real header format:
Change:
"https://www.googleapis.com/calendar/v3/calendars/cal1@example.com/events?alt=json",
"https://www.googleapis.com/calendar/v3/calendars/cal2@example.com/events?alt=json"
To:
"https://www.googleapis.com/calendar/v3/calendars/cal1%40example.com/events?alt=json",
"https://www.googleapis.com/calendar/v3/calendars/cal2%40example.com/events?alt=json"
Context for Agents
The docs example sets `googleCalendarWatchResourceUris` to URIs like `https://www.googleapis.com/calendar/v3/calendars/cal1@example.com/events?alt=json` (unencoded `@`). However, the docs earlier state "Store the `X-Goog-Resource-URI` string exactly as Google sends it: same encoding, path, and query string," and the test fixture uses `user%40example.com` (percent-encoded). The example curl payload uses unencoded `@` in the email addresses, which would fail to match Google's actual `X-Goog-Resource-URI` header (which uses `%40`). Update the example to use percent-encoded email addresses consistent with the real header format:
Change:
```
"https://www.googleapis.com/calendar/v3/calendars/cal1@example.com/events?alt=json",
"https://www.googleapis.com/calendar/v3/calendars/cal2@example.com/events?alt=json"
```
To:
```
"https://www.googleapis.com/calendar/v3/calendars/cal1%40example.com/events?alt=json",
"https://www.googleapis.com/calendar/v3/calendars/cal2%40example.com/events?alt=json"
```
File: docs/api-integrations/google-calendar/webhooks.mdx
Line: 320| await GoogleCalendarWebhookRouting.default(nangoMock as unknown as InternalNango, headers as any, {}, ''); | ||
|
|
||
| expect(mock).toHaveBeenCalledTimes(3); | ||
| expect(mock).toHaveBeenCalledTimes(4); |
There was a problem hiding this comment.
[Testing] The fallback chain test (tries metadata.googleCalendarWatchResourceUris first...) asserts toHaveBeenCalledTimes(4) and hard-codes all four call orderings, but the mock is set up at the describe-suite level — specifically, nangoMock.executeScriptForWebhooks = mock replaces the method on the instance right before the call. However, each it block creates its own nangoMock, so the mock from the first test cannot leak into subsequent ones. That's fine. What IS missing is a test for the case where x-goog-resource-uri is absent from a google-calendar webhook: the function will still try all three email-based fallbacks with an undefined connectionIdentifierValue. Add a test asserting the behavior when resourceUri is missing (does it return empty connectionIds cleanly, or does it make 3 spurious lookups?).
Context for Agents
The fallback chain test (`tries metadata.googleCalendarWatchResourceUris first...`) asserts `toHaveBeenCalledTimes(4)` and hard-codes all four call orderings, but the mock is set up at the describe-suite level — specifically, `nangoMock.executeScriptForWebhooks = mock` replaces the method on the instance right before the call. However, each `it` block creates its own `nangoMock`, so the mock from the first test cannot leak into subsequent ones. That's fine. What IS missing is a test for the case where `x-goog-resource-uri` is absent from a google-calendar webhook: the function will still try all three email-based fallbacks with an undefined `connectionIdentifierValue`. Add a test asserting the behavior when `resourceUri` is missing (does it return empty connectionIds cleanly, or does it make 3 spurious lookups?).
File: packages/server/lib/webhook/google-calendar-webhook-routing.unit.test.ts
Line: 40
Contio requires Google Calendar webhooks from multiple calendars per connection, not just the primary calendar. This PR updates the webhook routing and associated docs to make this possible. It also adds the same routing support to google integration since Contio uses both google-calendar and google integrations for calendar syncing.
NAN-5160: Contio - Add support for webhooks from multiple calendars
googleCalendarWatchResourceUrisThis PR also formalizes the connection-matching model by prioritizing raw
x-goog-resource-urimetadata matching for multi-calendar routing while preserving backward-compatible behavior for existing primary-calendar flows, and clarifies webhook behavior for unmatched and multi-match scenarios so integration behavior is more predictable across both Google integrations.This summary was automatically generated by @propel-code-bot