From 9f5d17d1f063a40520499bbf8e908552092137f0 Mon Sep 17 00:00:00 2001
From: neo773 <62795688+neo773@users.noreply.github.com>
Date: Mon, 13 Jul 2026 17:58:21 +0530
Subject: [PATCH] Add receipt metrics and logs to connected account sync
webhooks (#22853)
Webhook deliveries from Google and Microsoft were invisible at the app
level: successful notifications produced no logs and no metrics, so
webhook-triggered syncs could not be told apart from cron polling.
Add two counters, connected-account-sync-webhook/received/messaging and
/received/calendar, mirroring the sync-job metric umbrellas, and log a
line whenever a notification triggers a sync. Unmatched subscriptions
keep their existing warn logs.
---
.../metrics/types/metrics-keys.type.ts | 2 ++
.../google-calendar-notification.handler.ts | 20 +++++++++++++++-
.../google-messaging-notification.handler.ts | 24 ++++++++++++++++++-
.../google/google-webhook-driver.module.ts | 2 ++
...microsoft-calendar-notification.handler.ts | 14 +++++++++++
...icrosoft-messaging-notification.handler.ts | 14 +++++++++++
.../microsoft-webhook-driver.module.ts | 2 ++
7 files changed, 76 insertions(+), 2 deletions(-)
diff --git a/packages/twenty-server/src/engine/core-modules/metrics/types/metrics-keys.type.ts b/packages/twenty-server/src/engine/core-modules/metrics/types/metrics-keys.type.ts
index 5760c83a56..eac434bf4a 100644
--- a/packages/twenty-server/src/engine/core-modules/metrics/types/metrics-keys.type.ts
+++ b/packages/twenty-server/src/engine/core-modules/metrics/types/metrics-keys.type.ts
@@ -53,4 +53,6 @@ export enum MetricsKeys {
AiChatTurnCompleted = 'ai-chat/turn-completed',
AiChatTurnFailed = 'ai-chat/turn-failed',
WorkspaceMetadataCacheLocalEviction = 'workspace-metadata-cache/local-eviction',
+ ConnectedAccountSyncWebhookReceivedMessaging = 'connected-account-sync-webhook/received/messaging',
+ ConnectedAccountSyncWebhookReceivedCalendar = 'connected-account-sync-webhook/received/calendar',
}
diff --git a/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/google/google-calendar-notification.handler.ts b/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/google/google-calendar-notification.handler.ts
index bfa67b2a8e..8d4cb84410 100644
--- a/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/google/google-calendar-notification.handler.ts
+++ b/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/google/google-calendar-notification.handler.ts
@@ -1,6 +1,6 @@
import { timingSafeEqual } from 'crypto';
-import { Injectable } from '@nestjs/common';
+import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isNonEmptyString } from '@sniptt/guards';
@@ -8,6 +8,8 @@ import { WebhookSubscriptionStatus } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
+import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
+import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
import { WebhookSyncTriggerService } from 'src/modules/connected-account/webhook-subscription-manager/services/webhook-sync-trigger.service';
import {
@@ -21,13 +23,21 @@ const GOOGLE_CALENDAR_SYNC_RESOURCE_STATE = 'sync';
@Injectable()
export class GoogleCalendarNotificationHandler implements WebhookNotificationHandler {
+ private readonly logger = new Logger(GoogleCalendarNotificationHandler.name);
+
constructor(
@InjectRepository(CalendarChannelEntity)
private readonly calendarChannelRepository: Repository,
private readonly webhookSyncTriggerService: WebhookSyncTriggerService,
+ private readonly metricsService: MetricsService,
) {}
async handle(request: GoogleCalendarChannelNotification): Promise {
+ this.metricsService.incrementCounterBy({
+ key: MetricsKeys.ConnectedAccountSyncWebhookReceivedCalendar,
+ amount: 1,
+ });
+
if (request.resourceState === GOOGLE_CALENDAR_SYNC_RESOURCE_STATE) {
return;
}
@@ -44,6 +54,10 @@ export class GoogleCalendarNotificationHandler implements WebhookNotificationHan
});
if (!isDefined(calendarChannel)) {
+ this.logger.warn(
+ `No calendar channel found for Google channel ${request.channelId}`,
+ );
+
return;
}
@@ -72,5 +86,9 @@ export class GoogleCalendarNotificationHandler implements WebhookNotificationHan
calendarChannel.id,
calendarChannel.workspaceId,
);
+
+ this.logger.log(
+ `Triggered calendar sync for calendar channel ${calendarChannel.id} from Google notification`,
+ );
}
}
diff --git a/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/google/google-messaging-notification.handler.ts b/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/google/google-messaging-notification.handler.ts
index fd144b20ed..3960e51e5f 100644
--- a/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/google/google-messaging-notification.handler.ts
+++ b/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/google/google-messaging-notification.handler.ts
@@ -1,4 +1,4 @@
-import { Injectable } from '@nestjs/common';
+import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isNonEmptyString } from '@sniptt/guards';
@@ -10,6 +10,8 @@ import {
import { isDefined } from 'twenty-shared/utils';
import { In, Repository } from 'typeorm';
+import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
+import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
@@ -31,8 +33,11 @@ export type GoogleMessagingNotificationRequest = {
@Injectable()
export class GoogleMessagingNotificationHandler implements WebhookNotificationHandler {
+ private readonly logger = new Logger(GoogleMessagingNotificationHandler.name);
+
constructor(
private readonly twentyConfigService: TwentyConfigService,
+ private readonly metricsService: MetricsService,
@InjectRepository(ConnectedAccountEntity)
private readonly connectedAccountRepository: Repository,
@InjectRepository(MessageChannelEntity)
@@ -43,6 +48,11 @@ export class GoogleMessagingNotificationHandler implements WebhookNotificationHa
async handle(request: GoogleMessagingNotificationRequest): Promise {
await this.verify(request.authorizationHeader);
+ this.metricsService.incrementCounterBy({
+ key: MetricsKeys.ConnectedAccountSyncWebhookReceivedMessaging,
+ amount: 1,
+ });
+
const decodedData = this.decodeMessageData(request.body);
if (!isDefined(decodedData)) {
@@ -61,6 +71,10 @@ export class GoogleMessagingNotificationHandler implements WebhookNotificationHa
);
if (connectedAccountIds.length === 0) {
+ this.logger.warn(
+ 'No Google connected account matches Gmail notification',
+ );
+
return;
}
@@ -71,12 +85,20 @@ export class GoogleMessagingNotificationHandler implements WebhookNotificationHa
},
});
+ if (messageChannels.length === 0) {
+ return;
+ }
+
for (const messageChannel of messageChannels) {
await this.webhookSyncTriggerService.triggerMessagingSync(
messageChannel.id,
messageChannel.workspaceId,
);
}
+
+ this.logger.log(
+ `Triggered messaging sync for ${messageChannels.length} message channels from Gmail notification`,
+ );
}
private async verify(authorizationHeader: string | undefined): Promise {
diff --git a/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/google/google-webhook-driver.module.ts b/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/google/google-webhook-driver.module.ts
index cfc2423362..14cc3044e8 100644
--- a/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/google/google-webhook-driver.module.ts
+++ b/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/google/google-webhook-driver.module.ts
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
+import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
@@ -12,6 +13,7 @@ import { GoogleMessagingNotificationHandler } from 'src/modules/connected-accoun
@Module({
imports: [
+ MetricsModule,
TwentyConfigModule,
WebhookSubscriptionModule,
TypeOrmModule.forFeature([
diff --git a/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/microsoft/microsoft-calendar-notification.handler.ts b/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/microsoft/microsoft-calendar-notification.handler.ts
index b8fbaf84a6..1942e4fbaa 100644
--- a/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/microsoft/microsoft-calendar-notification.handler.ts
+++ b/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/microsoft/microsoft-calendar-notification.handler.ts
@@ -7,6 +7,8 @@ import { isNonEmptyString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { In, Repository } from 'typeorm';
+import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
+import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
import { CalendarWebhookSubscriptionService } from 'src/modules/connected-account/webhook-subscription-manager/services/calendar-webhook-subscription.service';
import { WebhookSyncTriggerService } from 'src/modules/connected-account/webhook-subscription-manager/services/webhook-sync-trigger.service';
@@ -26,9 +28,17 @@ export class MicrosoftCalendarNotificationHandler implements WebhookNotification
private readonly calendarChannelRepository: Repository,
private readonly calendarWebhookSubscriptionService: CalendarWebhookSubscriptionService,
private readonly webhookSyncTriggerService: WebhookSyncTriggerService,
+ private readonly metricsService: MetricsService,
) {}
async handle(notifications: MicrosoftGraphNotification[]): Promise {
+ if (notifications.length > 0) {
+ this.metricsService.incrementCounterBy({
+ key: MetricsKeys.ConnectedAccountSyncWebhookReceivedCalendar,
+ amount: notifications.length,
+ });
+ }
+
const subscriptionIds = notifications
.map((notification) => notification.subscriptionId)
.filter(isNonEmptyString);
@@ -96,6 +106,10 @@ export class MicrosoftCalendarNotificationHandler implements WebhookNotification
calendarChannel.id,
calendarChannel.workspaceId,
);
+
+ this.logger.log(
+ `Triggered calendar sync for calendar channel ${calendarChannel.id} from Microsoft notification`,
+ );
}
}
}
diff --git a/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/microsoft/microsoft-messaging-notification.handler.ts b/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/microsoft/microsoft-messaging-notification.handler.ts
index c33ebf0904..716c358e14 100644
--- a/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/microsoft/microsoft-messaging-notification.handler.ts
+++ b/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/microsoft/microsoft-messaging-notification.handler.ts
@@ -7,6 +7,8 @@ import { isNonEmptyString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { In, Repository } from 'typeorm';
+import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
+import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
import { MessagingWebhookSubscriptionService } from 'src/modules/connected-account/webhook-subscription-manager/services/messaging-webhook-subscription.service';
import { WebhookSyncTriggerService } from 'src/modules/connected-account/webhook-subscription-manager/services/webhook-sync-trigger.service';
@@ -26,9 +28,17 @@ export class MicrosoftMessagingNotificationHandler implements WebhookNotificatio
private readonly messageChannelRepository: Repository,
private readonly messagingWebhookSubscriptionService: MessagingWebhookSubscriptionService,
private readonly webhookSyncTriggerService: WebhookSyncTriggerService,
+ private readonly metricsService: MetricsService,
) {}
async handle(notifications: MicrosoftGraphNotification[]): Promise {
+ if (notifications.length > 0) {
+ this.metricsService.incrementCounterBy({
+ key: MetricsKeys.ConnectedAccountSyncWebhookReceivedMessaging,
+ amount: notifications.length,
+ });
+ }
+
const subscriptionIds = notifications
.map((notification) => notification.subscriptionId)
.filter(isNonEmptyString);
@@ -95,6 +105,10 @@ export class MicrosoftMessagingNotificationHandler implements WebhookNotificatio
messageChannel.id,
messageChannel.workspaceId,
);
+
+ this.logger.log(
+ `Triggered messaging sync for message channel ${messageChannel.id} from Microsoft notification`,
+ );
}
}
}
diff --git a/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/microsoft/microsoft-webhook-driver.module.ts b/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/microsoft/microsoft-webhook-driver.module.ts
index 8c9f4bab85..4b92d2e21b 100644
--- a/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/microsoft/microsoft-webhook-driver.module.ts
+++ b/packages/twenty-server/src/modules/connected-account-sync-webhooks/drivers/microsoft/microsoft-webhook-driver.module.ts
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
+import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
@@ -11,6 +12,7 @@ import { MicrosoftMessagingNotificationHandler } from 'src/modules/connected-acc
@Module({
imports: [
+ MetricsModule,
TwentyConfigModule,
WebhookSubscriptionModule,
TypeOrmModule.forFeature([MessageChannelEntity, CalendarChannelEntity]),