From 96ea1e1ffc85d7b16cea989a15d4a2ad13f3175a Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Mon, 3 Aug 2026 19:21:17 +0530 Subject: [PATCH] Track connected account webhook subscription lifecycle metrics (#23710) Emits created/renewed/deleted counters and their failure counterparts from the messaging and calendar webhook subscription services. Each counter carries channel_type and provider attributes so the Grafana panels can break them down. Infra side: twentyhq/twenty-infra#841 Co-authored-by: neo773 --- .../metrics/types/metrics-keys.type.ts | 6 +++ .../calendar-webhook-subscription.service.ts | 46 +++++++++++++++++++ .../messaging-webhook-subscription.service.ts | 46 +++++++++++++++++++ .../webhook-subscription.module.ts | 2 + 4 files changed, 100 insertions(+) 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 aa242b7676..736b484e0c 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 @@ -77,4 +77,10 @@ export enum MetricsKeys { SdkClientGenerationDurationMs = 'sdk-client-generation/duration-ms', ConnectedAccountSyncWebhookReceivedMessaging = 'connected-account-sync-webhook/received/messaging', ConnectedAccountSyncWebhookReceivedCalendar = 'connected-account-sync-webhook/received/calendar', + ConnectedAccountWebhookSubscriptionCreated = 'connected-account-webhook-subscription/created', + ConnectedAccountWebhookSubscriptionCreationFailed = 'connected-account-webhook-subscription/creation-failed', + ConnectedAccountWebhookSubscriptionRenewed = 'connected-account-webhook-subscription/renewed', + ConnectedAccountWebhookSubscriptionRenewalFailed = 'connected-account-webhook-subscription/renewal-failed', + ConnectedAccountWebhookSubscriptionDeleted = 'connected-account-webhook-subscription/deleted', + ConnectedAccountWebhookSubscriptionDeletionFailed = 'connected-account-webhook-subscription/deletion-failed', } diff --git a/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/services/calendar-webhook-subscription.service.ts b/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/services/calendar-webhook-subscription.service.ts index d10743657e..8b48e56771 100644 --- a/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/services/calendar-webhook-subscription.service.ts +++ b/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/services/calendar-webhook-subscription.service.ts @@ -10,6 +10,8 @@ import { Repository } from 'typeorm'; import { v4 } from 'uuid'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +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 { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { WebhookSubscriptionDriverFactory } from 'src/modules/connected-account/webhook-subscription-manager/services/webhook-subscription-driver-factory.service'; @@ -24,6 +26,7 @@ export class CalendarWebhookSubscriptionService { private readonly calendarChannelRepository: Repository, private readonly webhookSubscriptionDriverFactory: WebhookSubscriptionDriverFactory, private readonly exceptionHandlerService: ExceptionHandlerService, + private readonly metricsService: MetricsService, ) {} async createSubscription( @@ -82,6 +85,12 @@ export class CalendarWebhookSubscriptionService { webhookSubscriptionStatus: WebhookSubscriptionStatus.ACTIVE, webhookSubscriptionExpiresAt: result.expiresAt, }); + + this.metricsService.incrementCounterBy({ + key: MetricsKeys.ConnectedAccountWebhookSubscriptionCreated, + amount: 1, + attributes: this.buildMetricAttributes(connectedAccount.provider), + }); } catch (error) { await this.calendarChannelRepository.update(calendarChannel.id, { webhookSubscriptionClientState: clientState, @@ -89,6 +98,12 @@ export class CalendarWebhookSubscriptionService { webhookSubscriptionExpiresAt: null, }); + this.metricsService.incrementCounterBy({ + key: MetricsKeys.ConnectedAccountWebhookSubscriptionCreationFailed, + amount: 1, + attributes: this.buildMetricAttributes(connectedAccount.provider), + }); + this.exceptionHandlerService.captureExceptions([error], { workspace: { id: workspaceId }, }); @@ -149,11 +164,23 @@ export class CalendarWebhookSubscriptionService { webhookSubscriptionStatus: WebhookSubscriptionStatus.ACTIVE, webhookSubscriptionExpiresAt: result.expiresAt, }); + + this.metricsService.incrementCounterBy({ + key: MetricsKeys.ConnectedAccountWebhookSubscriptionRenewed, + amount: 1, + attributes: this.buildMetricAttributes(connectedAccount.provider), + }); } catch (error) { await this.calendarChannelRepository.update(calendarChannel.id, { webhookSubscriptionStatus: WebhookSubscriptionStatus.FAILED, }); + this.metricsService.incrementCounterBy({ + key: MetricsKeys.ConnectedAccountWebhookSubscriptionRenewalFailed, + amount: 1, + attributes: this.buildMetricAttributes(connectedAccount.provider), + }); + this.exceptionHandlerService.captureExceptions([error], { workspace: { id: calendarChannel.workspaceId }, }); @@ -191,13 +218,32 @@ export class CalendarWebhookSubscriptionService { try { await driver.deleteSubscription(this.toContext(calendarChannel)); + + this.metricsService.incrementCounterBy({ + key: MetricsKeys.ConnectedAccountWebhookSubscriptionDeleted, + amount: 1, + attributes: this.buildMetricAttributes(connectedAccount.provider), + }); } catch (error) { + this.metricsService.incrementCounterBy({ + key: MetricsKeys.ConnectedAccountWebhookSubscriptionDeletionFailed, + amount: 1, + attributes: this.buildMetricAttributes(connectedAccount.provider), + }); + this.exceptionHandlerService.captureExceptions([error], { workspace: { id: calendarChannel.workspaceId }, }); } } + private buildMetricAttributes(provider: string) { + return { + channel_type: WebhookSubscriptionChannelType.CALENDAR, + provider, + }; + } + private toContext( calendarChannel: CalendarChannelEntity, ): WebhookSubscriptionContext { diff --git a/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/services/messaging-webhook-subscription.service.ts b/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/services/messaging-webhook-subscription.service.ts index 5f7f7b82dd..e11b6f07fb 100644 --- a/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/services/messaging-webhook-subscription.service.ts +++ b/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/services/messaging-webhook-subscription.service.ts @@ -10,6 +10,8 @@ import { Repository } from 'typeorm'; import { v4 } from 'uuid'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; +import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type'; 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'; import { WebhookSubscriptionDriverFactory } from 'src/modules/connected-account/webhook-subscription-manager/services/webhook-subscription-driver-factory.service'; @@ -24,6 +26,7 @@ export class MessagingWebhookSubscriptionService { private readonly messageChannelRepository: Repository, private readonly webhookSubscriptionDriverFactory: WebhookSubscriptionDriverFactory, private readonly exceptionHandlerService: ExceptionHandlerService, + private readonly metricsService: MetricsService, ) {} async createSubscription( @@ -81,6 +84,12 @@ export class MessagingWebhookSubscriptionService { webhookSubscriptionStatus: WebhookSubscriptionStatus.ACTIVE, webhookSubscriptionExpiresAt: result.expiresAt, }); + + this.metricsService.incrementCounterBy({ + key: MetricsKeys.ConnectedAccountWebhookSubscriptionCreated, + amount: 1, + attributes: this.buildMetricAttributes(connectedAccount.provider), + }); } catch (error) { await this.messageChannelRepository.update(messageChannel.id, { webhookSubscriptionClientState: clientState, @@ -88,6 +97,12 @@ export class MessagingWebhookSubscriptionService { webhookSubscriptionExpiresAt: null, }); + this.metricsService.incrementCounterBy({ + key: MetricsKeys.ConnectedAccountWebhookSubscriptionCreationFailed, + amount: 1, + attributes: this.buildMetricAttributes(connectedAccount.provider), + }); + this.exceptionHandlerService.captureExceptions([error], { workspace: { id: workspaceId }, }); @@ -147,11 +162,23 @@ export class MessagingWebhookSubscriptionService { webhookSubscriptionStatus: WebhookSubscriptionStatus.ACTIVE, webhookSubscriptionExpiresAt: result.expiresAt, }); + + this.metricsService.incrementCounterBy({ + key: MetricsKeys.ConnectedAccountWebhookSubscriptionRenewed, + amount: 1, + attributes: this.buildMetricAttributes(connectedAccount.provider), + }); } catch (error) { await this.messageChannelRepository.update(messageChannel.id, { webhookSubscriptionStatus: WebhookSubscriptionStatus.FAILED, }); + this.metricsService.incrementCounterBy({ + key: MetricsKeys.ConnectedAccountWebhookSubscriptionRenewalFailed, + amount: 1, + attributes: this.buildMetricAttributes(connectedAccount.provider), + }); + this.exceptionHandlerService.captureExceptions([error], { workspace: { id: messageChannel.workspaceId }, }); @@ -189,13 +216,32 @@ export class MessagingWebhookSubscriptionService { try { await driver.deleteSubscription(this.toContext(messageChannel)); + + this.metricsService.incrementCounterBy({ + key: MetricsKeys.ConnectedAccountWebhookSubscriptionDeleted, + amount: 1, + attributes: this.buildMetricAttributes(connectedAccount.provider), + }); } catch (error) { + this.metricsService.incrementCounterBy({ + key: MetricsKeys.ConnectedAccountWebhookSubscriptionDeletionFailed, + amount: 1, + attributes: this.buildMetricAttributes(connectedAccount.provider), + }); + this.exceptionHandlerService.captureExceptions([error], { workspace: { id: messageChannel.workspaceId }, }); } } + private buildMetricAttributes(provider: string) { + return { + channel_type: WebhookSubscriptionChannelType.MESSAGING, + provider, + }; + } + private toContext( messageChannel: MessageChannelEntity, ): WebhookSubscriptionContext { diff --git a/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/webhook-subscription.module.ts b/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/webhook-subscription.module.ts index 55799676bb..fbde141ba2 100644 --- a/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/webhook-subscription.module.ts +++ b/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/webhook-subscription.module.ts @@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; +import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; @@ -21,6 +22,7 @@ import { WebhookSubscriptionManagerModule } from 'src/modules/connected-account/ imports: [ WebhookSubscriptionManagerModule, FeatureFlagModule, + MetricsModule, WorkspaceIteratorModule, TypeOrmModule.forFeature([ WorkspaceEntity,