From 6bd565681f0dfa043826b27f609f14372f17b7af Mon Sep 17 00:00:00 2001 From: d-klotz Date: Mon, 19 Jan 2026 15:49:29 -0300 Subject: [PATCH 1/2] feat(zoho-crm): add updateNotification method for channel renewal Add method to update/renew Zoho CRM notification channels before expiration. Zoho notification channels expire after max 7 days, so integrations need to periodically renew them. ## Changes - Add `updateNotification(body)` method using PATCH endpoint - Same validation as enableNotification (channel_id, events, notify_url, token) - Uses `/crm/v8/actions/watch` endpoint with PATCH method ## API Documentation @see https://www.zoho.com/crm/developer/docs/api/v8/notifications/update.html ## Usage ```typescript await api.updateNotification({ watch: [{ channel_id: existingChannelId, events: ['Contacts.all', 'Accounts.all'], channel_expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), token: verificationToken, notify_url: webhookUrl, }], }); ``` Co-Authored-By: Claude Opus 4.5 --- packages/v1-ready/zoho-crm/src/api.ts | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/v1-ready/zoho-crm/src/api.ts b/packages/v1-ready/zoho-crm/src/api.ts index 06ddfd5..7028915 100644 --- a/packages/v1-ready/zoho-crm/src/api.ts +++ b/packages/v1-ready/zoho-crm/src/api.ts @@ -618,6 +618,44 @@ export class Api extends OAuth2Requester { } } + /** + * Update/renew notification channel configuration + * Use this to extend the channel_expiry before it expires (max 7 days from now) + * @param body - Notification configuration with watch items to update + * @returns Promise Response with updated channel details + * @see https://www.zoho.com/crm/developer/docs/api/v8/notifications/update.html + */ + async updateNotification(body: NotificationWatchConfig): Promise { + if (!body || !body.watch || !Array.isArray(body.watch)) { + throw new Error('Body must contain watch array'); + } + + // Validate each watch item + body.watch.forEach((item, index) => { + if (!item.channel_id) { + throw new Error(`watch[${index}].channel_id is required`); + } + if (!item.events || !Array.isArray(item.events) || item.events.length === 0) { + throw new Error(`watch[${index}].events must be a non-empty array`); + } + if (!item.notify_url) { + throw new Error(`watch[${index}].notify_url is required`); + } + if (item.token && item.token.length > 50) { + throw new Error(`watch[${index}].token must be 50 characters or less`); + } + }); + + try { + return await this._patch({ + url: this.baseUrl + this.URLs.notificationsWatch, + body: body, + }); + } catch (error) { + throw error; + } + } + /** * Disable notification channels * @param channelIds - Array of channel IDs to disable From 08b1c2c20466df8d54c0255fa93d29b3953bb7fb Mon Sep 17 00:00:00 2001 From: d-klotz Date: Tue, 20 Jan 2026 15:42:08 -0300 Subject: [PATCH 2/2] feat(zoho-crm): add date formatting for Zoho API requests --- packages/v1-ready/zoho-crm/src/api.ts | 29 +++++++++++++++++-- .../v1-ready/zoho-crm/src/frigg-core.d.ts | 1 + 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/v1-ready/zoho-crm/src/api.ts b/packages/v1-ready/zoho-crm/src/api.ts index 7028915..c1ebde9 100644 --- a/packages/v1-ready/zoho-crm/src/api.ts +++ b/packages/v1-ready/zoho-crm/src/api.ts @@ -43,6 +43,15 @@ const LOCATION_CONFIG: Record = const DEFAULT_LOCATION: ZohoLocation = 'us'; +/** + * Formats datetime for Zoho API (removes milliseconds, converts Z to +00:00) + * Zoho expects format: 2019-05-02T15:00:00+05:30 + */ +function formatDateTimeForZoho(dateStr: string | undefined): string | undefined { + if (!dateStr) return dateStr; + return dateStr.replace(/\.\d{3}Z$/, '+00:00').replace(/Z$/, '+00:00'); +} + export class Api extends OAuth2Requester { public URLs: Record string)>; public location: ZohoLocation; @@ -608,10 +617,18 @@ export class Api extends OAuth2Requester { } }); + const formattedBody: NotificationWatchConfig = { + ...body, + watch: body.watch.map(item => ({ + ...item, + ...(item.channel_expiry && { channel_expiry: formatDateTimeForZoho(item.channel_expiry) }) + })) + }; + try { return await this._post({ url: this.baseUrl + this.URLs.notificationsWatch, - body: body, + body: formattedBody, }); } catch (error) { throw error; @@ -646,10 +663,18 @@ export class Api extends OAuth2Requester { } }); + const formattedBody: NotificationWatchConfig = { + ...body, + watch: body.watch.map(item => ({ + ...item, + ...(item.channel_expiry && { channel_expiry: formatDateTimeForZoho(item.channel_expiry) }) + })) + }; + try { return await this._patch({ url: this.baseUrl + this.URLs.notificationsWatch, - body: body, + body: formattedBody, }); } catch (error) { throw error; diff --git a/packages/v1-ready/zoho-crm/src/frigg-core.d.ts b/packages/v1-ready/zoho-crm/src/frigg-core.d.ts index 8a13af8..7360588 100644 --- a/packages/v1-ready/zoho-crm/src/frigg-core.d.ts +++ b/packages/v1-ready/zoho-crm/src/frigg-core.d.ts @@ -16,6 +16,7 @@ declare module '@friggframework/core' { parsedBody(response: any): Promise; _get(options: any, stringify?: boolean): Promise; _post(options: any, stringify?: boolean): Promise; + _patch(options: any): Promise; _put(options: any, stringify?: boolean): Promise; _delete(options: any): Promise; }