From c04714b9e04d2db82749d9d037f96046710944ab Mon Sep 17 00:00:00 2001
From: neo773 <62795688+neo773@users.noreply.github.com>
Date: Mon, 20 Jul 2026 17:26:47 +0530
Subject: [PATCH] fix(messaging): register and harden webhook subscription
renewal cron (#23006)
The renewal cron was never wired into cron:register:all, so
Gmail/Calendar/Graph watches were never renewed and went dark ~7 days
after connect (the max watch lifetime all three providers allow).
Register it, fan renewals out as per-channel queue jobs scoped to active
workspaces, retry FAILED channels, and recreate Google Calendar watches
before stopping the old one.
---
.../commands/cron-register-all.command.ts | 7 +
.../commands/database-command.module.ts | 2 +
...microsoft-calendar-notification.handler.ts | 14 +-
...icrosoft-messaging-notification.handler.ts | 14 +-
.../webhook-subscription-renewal.cron.job.ts | 153 ++++++++++++------
.../google-webhook-subscription.driver.ts | 12 +-
.../jobs/renew-webhook-subscription.job.ts | 41 +++++
.../calendar-webhook-subscription.service.ts | 52 ++++--
.../messaging-webhook-subscription.service.ts | 50 ++++--
.../webhook-subscription.module.ts | 5 +
10 files changed, 266 insertions(+), 84 deletions(-)
create mode 100644 packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/jobs/renew-webhook-subscription.job.ts
diff --git a/packages/twenty-server/src/database/commands/cron-register-all.command.ts b/packages/twenty-server/src/database/commands/cron-register-all.command.ts
index 8588fab285..cb03764c5a 100644
--- a/packages/twenty-server/src/database/commands/cron-register-all.command.ts
+++ b/packages/twenty-server/src/database/commands/cron-register-all.command.ts
@@ -15,6 +15,7 @@ import { CronTriggerCronCommand } from 'src/engine/core-modules/logic-function/l
import { CheckPublicDomainsValidRecordsCronCommand } from 'src/engine/core-modules/public-domain/crons/commands/check-public-domains-valid-records.cron.command';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { CheckCustomDomainValidRecordsCronCommand } from 'src/engine/core-modules/workspace/crons/commands/check-custom-domain-valid-records.cron.command';
+import { WebhookSubscriptionRenewalCronCommand } from 'src/modules/connected-account/webhook-subscription-manager/crons/commands/webhook-subscription-renewal.cron.command';
import { TrashCleanupCronCommand } from 'src/engine/trash-cleanup/commands/trash-cleanup.cron.command';
import { CleanOnboardingWorkspacesCronCommand } from 'src/engine/workspace-manager/workspace-cleaner/commands/clean-onboarding-workspaces.cron.command';
import { CleanSuspendedWorkspacesCronCommand } from 'src/engine/workspace-manager/workspace-cleaner/commands/clean-suspended-workspaces.cron.command';
@@ -49,6 +50,8 @@ export class CronRegisterAllCommand extends CommandRunner {
private readonly calendarOngoingStaleCronCommand: CalendarOngoingStaleCronCommand,
private readonly calendarRelaunchFailedCalendarChannelsCronCommand: CalendarRelaunchFailedCalendarChannelsCronCommand,
+ private readonly webhookSubscriptionRenewalCronCommand: WebhookSubscriptionRenewalCronCommand,
+
private readonly workflowCronTriggerCronCommand: WorkflowCronTriggerCronCommand,
private readonly workflowRunEnqueueCronCommand: WorkflowRunEnqueueCronCommand,
private readonly workflowHandleStaledRunsCronCommand: WorkflowHandleStaledRunsCronCommand,
@@ -119,6 +122,10 @@ export class CronRegisterAllCommand extends CommandRunner {
name: 'CalendarRelaunchFailedCalendarChannels',
command: this.calendarRelaunchFailedCalendarChannelsCronCommand,
},
+ {
+ name: 'WebhookSubscriptionRenewal',
+ command: this.webhookSubscriptionRenewalCronCommand,
+ },
{
name: 'CheckCustomDomainValidRecords',
command: this.checkCustomDomainValidRecordsCronCommand,
diff --git a/packages/twenty-server/src/database/commands/database-command.module.ts b/packages/twenty-server/src/database/commands/database-command.module.ts
index 060bd295fa..86301c4666 100644
--- a/packages/twenty-server/src/database/commands/database-command.module.ts
+++ b/packages/twenty-server/src/database/commands/database-command.module.ts
@@ -48,6 +48,7 @@ import { WorkspaceCleanerModule } from 'src/engine/workspace-manager/workspace-c
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-version/workspace-version.module';
+import { WebhookSubscriptionModule } from 'src/modules/connected-account/webhook-subscription-manager/webhook-subscription.module';
import { CalendarEventImportManagerModule } from 'src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module';
import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module';
import { WorkflowRunQueueModule } from 'src/modules/workflow/workflow-runner/workflow-run-queue/workflow-run-queue.module';
@@ -61,6 +62,7 @@ import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/au
// Cron command dependencies
MessagingImportManagerModule,
CalendarEventImportManagerModule,
+ WebhookSubscriptionModule,
AutomatedTriggerModule,
FileModule,
WorkspaceModule,
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 1942e4fbaa..a3577f2da7 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
@@ -96,9 +96,17 @@ export class MicrosoftCalendarNotificationHandler implements WebhookNotification
isNonEmptyString(notification.lifecycleEvent) &&
notification.lifecycleEvent !== 'missed'
) {
- await this.calendarWebhookSubscriptionService.renewSubscription(
- calendarChannel,
- );
+ try {
+ await this.calendarWebhookSubscriptionService.renewSubscription({
+ calendarChannelId: calendarChannel.id,
+ workspaceId: calendarChannel.workspaceId,
+ });
+ } catch (error) {
+ this.logger.error(
+ `Failed to renew calendar subscription for channel ${calendarChannel.id}`,
+ error,
+ );
+ }
continue;
}
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 716c358e14..83111b0d13 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
@@ -95,9 +95,17 @@ export class MicrosoftMessagingNotificationHandler implements WebhookNotificatio
isNonEmptyString(notification.lifecycleEvent) &&
notification.lifecycleEvent !== 'missed'
) {
- await this.messagingWebhookSubscriptionService.renewSubscription(
- messageChannel,
- );
+ try {
+ await this.messagingWebhookSubscriptionService.renewSubscription({
+ messageChannelId: messageChannel.id,
+ workspaceId: messageChannel.workspaceId,
+ });
+ } catch (error) {
+ this.logger.error(
+ `Failed to renew messaging subscription for channel ${messageChannel.id}`,
+ error,
+ );
+ }
continue;
}
diff --git a/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/crons/jobs/webhook-subscription-renewal.cron.job.ts b/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/crons/jobs/webhook-subscription-renewal.cron.job.ts
index 7572982f3a..e2ab20205c 100644
--- a/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/crons/jobs/webhook-subscription-renewal.cron.job.ts
+++ b/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/crons/jobs/webhook-subscription-renewal.cron.job.ts
@@ -1,31 +1,46 @@
import { Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
-import { WebhookSubscriptionStatus } from 'twenty-shared/types';
-import { LessThanOrEqual, Repository } from 'typeorm';
+import {
+ WebhookSubscriptionChannelType,
+ WebhookSubscriptionStatus,
+} from 'twenty-shared/types';
+import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
+import { type FindManyOptions, In, LessThanOrEqual, Repository } from 'typeorm';
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
+import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
+import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
+import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
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';
import { WEBHOOK_SUBSCRIPTION_RENEWAL_BUFFER_MS } from 'src/modules/connected-account/webhook-subscription-manager/constants/webhook-subscription-renewal-buffer-ms.constant';
import { WEBHOOK_SUBSCRIPTION_RENEWAL_CRON_PATTERN } from 'src/modules/connected-account/webhook-subscription-manager/constants/webhook-subscription-renewal-cron-pattern.constant';
-import { CalendarWebhookSubscriptionService } from 'src/modules/connected-account/webhook-subscription-manager/services/calendar-webhook-subscription.service';
-import { MessagingWebhookSubscriptionService } from 'src/modules/connected-account/webhook-subscription-manager/services/messaging-webhook-subscription.service';
+import {
+ RenewWebhookSubscriptionJob,
+ type RenewWebhookSubscriptionJobData,
+} from 'src/modules/connected-account/webhook-subscription-manager/jobs/renew-webhook-subscription.job';
+
+type WebhookSubscribableChannel = MessageChannelEntity | CalendarChannelEntity;
+
+type StaleChannel = Pick;
@Processor(MessageQueue.cronQueue)
export class WebhookSubscriptionRenewalCronJob {
private readonly logger = new Logger(WebhookSubscriptionRenewalCronJob.name);
constructor(
+ @InjectRepository(WorkspaceEntity)
+ private readonly workspaceRepository: Repository,
@InjectRepository(MessageChannelEntity)
private readonly messageChannelRepository: Repository,
@InjectRepository(CalendarChannelEntity)
private readonly calendarChannelRepository: Repository,
- private readonly messagingWebhookSubscriptionService: MessagingWebhookSubscriptionService,
- private readonly calendarWebhookSubscriptionService: CalendarWebhookSubscriptionService,
+ @InjectMessageQueue(MessageQueue.webhookQueue)
+ private readonly webhookQueueService: MessageQueueService,
) {}
@Process(WebhookSubscriptionRenewalCronJob.name)
@@ -34,59 +49,91 @@ export class WebhookSubscriptionRenewalCronJob {
WEBHOOK_SUBSCRIPTION_RENEWAL_CRON_PATTERN,
)
async handle(): Promise {
+ const activeWorkspaces = await this.workspaceRepository.find({
+ where: { activationStatus: WorkspaceActivationStatus.ACTIVE },
+ select: { id: true },
+ });
+
+ const activeWorkspaceIds = activeWorkspaces.map(
+ (workspace) => workspace.id,
+ );
+
+ if (activeWorkspaceIds.length === 0) {
+ return;
+ }
+
+ const [messageChannels, calendarChannels] = await Promise.all([
+ this.findStaleChannels(this.messageChannelRepository, activeWorkspaceIds),
+ this.findStaleChannels(
+ this.calendarChannelRepository,
+ activeWorkspaceIds,
+ ),
+ ]);
+
+ if (messageChannels.length === 0 && calendarChannels.length === 0) {
+ return;
+ }
+
+ await Promise.all([
+ this.enqueueRenewals(
+ WebhookSubscriptionChannelType.MESSAGING,
+ messageChannels,
+ ),
+ this.enqueueRenewals(
+ WebhookSubscriptionChannelType.CALENDAR,
+ calendarChannels,
+ ),
+ ]);
+
+ this.logger.log(
+ `Enqueued webhook subscription renewals: ${messageChannels.length} messaging, ${calendarChannels.length} calendar`,
+ );
+ }
+
+ private findStaleChannels(
+ repository: Repository,
+ activeWorkspaceIds: string[],
+ ): Promise {
+ const workspaceScope = { workspaceId: In(activeWorkspaceIds) };
const renewalThreshold = new Date(
Date.now() + WEBHOOK_SUBSCRIPTION_RENEWAL_BUFFER_MS,
);
- const expiringMessageChannels = await this.messageChannelRepository.find({
- where: {
- webhookSubscriptionStatus: WebhookSubscriptionStatus.ACTIVE,
- webhookSubscriptionExpiresAt: LessThanOrEqual(renewalThreshold),
- },
- });
+ const options: FindManyOptions = {
+ where: [
+ {
+ ...workspaceScope,
+ webhookSubscriptionStatus: WebhookSubscriptionStatus.FAILED,
+ },
+ {
+ ...workspaceScope,
+ webhookSubscriptionStatus: WebhookSubscriptionStatus.ACTIVE,
+ webhookSubscriptionExpiresAt: LessThanOrEqual(renewalThreshold),
+ },
+ ],
+ select: { id: true, workspaceId: true },
+ };
- const expiringCalendarChannels = await this.calendarChannelRepository.find({
- where: {
- webhookSubscriptionStatus: WebhookSubscriptionStatus.ACTIVE,
- webhookSubscriptionExpiresAt: LessThanOrEqual(renewalThreshold),
- },
- });
+ return repository.find(options as FindManyOptions);
+ }
- const expiringSubscriptionCount =
- expiringMessageChannels.length + expiringCalendarChannels.length;
-
- if (expiringSubscriptionCount === 0) {
- return;
- }
-
- this.logger.log(
- `Renewing ${expiringSubscriptionCount} webhook subscriptions`,
- );
-
- for (const messageChannel of expiringMessageChannels) {
- try {
- await this.messagingWebhookSubscriptionService.renewSubscription(
- messageChannel,
- );
- } catch (error) {
- this.logger.warn(
- `Failed to renew messaging webhook subscription for channel ${messageChannel.id}`,
- error,
- );
- }
- }
-
- for (const calendarChannel of expiringCalendarChannels) {
- try {
- await this.calendarWebhookSubscriptionService.renewSubscription(
- calendarChannel,
- );
- } catch (error) {
- this.logger.warn(
- `Failed to renew calendar webhook subscription for channel ${calendarChannel.id}`,
- error,
- );
- }
+ private async enqueueRenewals(
+ channelType: WebhookSubscriptionChannelType,
+ channels: StaleChannel[],
+ ): Promise {
+ for (const channel of channels) {
+ await this.webhookQueueService.add(
+ RenewWebhookSubscriptionJob.name,
+ {
+ channelType,
+ channelId: channel.id,
+ workspaceId: channel.workspaceId,
+ },
+ {
+ id: `${RenewWebhookSubscriptionJob.name}:${channelType}:${channel.id}`,
+ retryLimit: 3,
+ },
+ );
}
}
}
diff --git a/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/drivers/google/google-webhook-subscription.driver.ts b/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/drivers/google/google-webhook-subscription.driver.ts
index f333a45ee0..a27953b684 100644
--- a/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/drivers/google/google-webhook-subscription.driver.ts
+++ b/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/drivers/google/google-webhook-subscription.driver.ts
@@ -41,7 +41,17 @@ export class GoogleWebhookSubscriptionDriver implements WebhookSubscriptionDrive
context: WebhookSubscriptionContext,
): Promise {
if (context.channelType === WebhookSubscriptionChannelType.CALENDAR) {
- await this.deleteSubscription(context);
+ // Google Calendar watches can't be extended, only replaced by a fresh channel.
+ // Recreate before stopping the old watch so a failed recreate never leaves the account with no live watch.
+ const result = await this.createSubscription(
+ context.connectedAccountId,
+ context.channelType,
+ context.clientState,
+ );
+
+ await this.deleteSubscription(context).catch(() => undefined);
+
+ return result;
}
return this.createSubscription(
diff --git a/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/jobs/renew-webhook-subscription.job.ts b/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/jobs/renew-webhook-subscription.job.ts
new file mode 100644
index 0000000000..ee80dc21d4
--- /dev/null
+++ b/packages/twenty-server/src/modules/connected-account/webhook-subscription-manager/jobs/renew-webhook-subscription.job.ts
@@ -0,0 +1,41 @@
+import { WebhookSubscriptionChannelType } from 'twenty-shared/types';
+
+import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
+import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
+import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
+import { CalendarWebhookSubscriptionService } from 'src/modules/connected-account/webhook-subscription-manager/services/calendar-webhook-subscription.service';
+import { MessagingWebhookSubscriptionService } from 'src/modules/connected-account/webhook-subscription-manager/services/messaging-webhook-subscription.service';
+
+export type RenewWebhookSubscriptionJobData = {
+ channelType: WebhookSubscriptionChannelType;
+ channelId: string;
+ workspaceId: string;
+};
+
+@Processor(MessageQueue.webhookQueue)
+export class RenewWebhookSubscriptionJob {
+ constructor(
+ private readonly messagingWebhookSubscriptionService: MessagingWebhookSubscriptionService,
+ private readonly calendarWebhookSubscriptionService: CalendarWebhookSubscriptionService,
+ ) {}
+
+ @Process(RenewWebhookSubscriptionJob.name)
+ async handle(data: RenewWebhookSubscriptionJobData): Promise {
+ const { channelType, channelId, workspaceId } = data;
+
+ switch (channelType) {
+ case WebhookSubscriptionChannelType.MESSAGING:
+ await this.messagingWebhookSubscriptionService.renewSubscription({
+ messageChannelId: channelId,
+ workspaceId,
+ });
+ break;
+ case WebhookSubscriptionChannelType.CALENDAR:
+ await this.calendarWebhookSubscriptionService.renewSubscription({
+ calendarChannelId: channelId,
+ workspaceId,
+ });
+ break;
+ }
+ }
+}
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 b90135c697..d10743657e 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
@@ -61,11 +61,12 @@ export class CalendarWebhookSubscriptionService {
connectedAccount.provider,
);
- if (isDefined(calendarChannel.webhookSubscriptionExternalId)) {
- await driver
- .deleteSubscription(this.toContext(calendarChannel))
- .catch(() => undefined);
- }
+ // Keep any existing watch live until the replacement is created, then stop it.
+ const previousSubscription = isDefined(
+ calendarChannel.webhookSubscriptionExternalId,
+ )
+ ? this.toContext(calendarChannel)
+ : null;
try {
const result = await driver.createSubscription(
@@ -94,18 +95,41 @@ export class CalendarWebhookSubscriptionService {
throw error;
}
+
+ if (isDefined(previousSubscription)) {
+ await driver
+ .deleteSubscription(previousSubscription)
+ .catch(() => undefined);
+ }
}
- async renewSubscription(
- calendarChannel: CalendarChannelEntity,
- ): Promise {
- const connectedAccount = await this.connectedAccountRepository.findOne({
- where: {
- id: calendarChannel.connectedAccountId,
- workspaceId: calendarChannel.workspaceId,
- },
+ async renewSubscription({
+ calendarChannelId,
+ workspaceId,
+ }: {
+ calendarChannelId: string;
+ workspaceId: string;
+ }): Promise {
+ const calendarChannel = await this.calendarChannelRepository.findOne({
+ where: { id: calendarChannelId, workspaceId },
+ relations: ['connectedAccount'],
});
+ if (!isDefined(calendarChannel)) {
+ return;
+ }
+
+ if (
+ calendarChannel.webhookSubscriptionStatus !==
+ WebhookSubscriptionStatus.ACTIVE
+ ) {
+ await this.createSubscription(calendarChannelId, workspaceId);
+
+ return;
+ }
+
+ const { connectedAccount } = calendarChannel;
+
if (!isDefined(connectedAccount)) {
return;
}
@@ -133,6 +157,8 @@ export class CalendarWebhookSubscriptionService {
this.exceptionHandlerService.captureExceptions([error], {
workspace: { id: calendarChannel.workspaceId },
});
+
+ throw error;
}
}
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 4e3d1e858f..5f7f7b82dd 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
@@ -61,11 +61,12 @@ export class MessagingWebhookSubscriptionService {
connectedAccount.provider,
);
- if (isDefined(messageChannel.webhookSubscriptionExternalId)) {
- await driver
- .deleteSubscription(this.toContext(messageChannel))
- .catch(() => undefined);
- }
+ // Keep any existing watch live until the replacement is created, then stop it.
+ const previousSubscription = isDefined(
+ messageChannel.webhookSubscriptionExternalId,
+ )
+ ? this.toContext(messageChannel)
+ : null;
try {
const result = await driver.createSubscription(
@@ -93,16 +94,41 @@ export class MessagingWebhookSubscriptionService {
throw error;
}
+
+ if (isDefined(previousSubscription)) {
+ await driver
+ .deleteSubscription(previousSubscription)
+ .catch(() => undefined);
+ }
}
- async renewSubscription(messageChannel: MessageChannelEntity): Promise {
- const connectedAccount = await this.connectedAccountRepository.findOne({
- where: {
- id: messageChannel.connectedAccountId,
- workspaceId: messageChannel.workspaceId,
- },
+ async renewSubscription({
+ messageChannelId,
+ workspaceId,
+ }: {
+ messageChannelId: string;
+ workspaceId: string;
+ }): Promise {
+ const messageChannel = await this.messageChannelRepository.findOne({
+ where: { id: messageChannelId, workspaceId },
+ relations: ['connectedAccount'],
});
+ if (!isDefined(messageChannel)) {
+ return;
+ }
+
+ if (
+ messageChannel.webhookSubscriptionStatus !==
+ WebhookSubscriptionStatus.ACTIVE
+ ) {
+ await this.createSubscription(messageChannelId, workspaceId);
+
+ return;
+ }
+
+ const { connectedAccount } = messageChannel;
+
if (!isDefined(connectedAccount)) {
return;
}
@@ -129,6 +155,8 @@ export class MessagingWebhookSubscriptionService {
this.exceptionHandlerService.captureExceptions([error], {
workspace: { id: messageChannel.workspaceId },
});
+
+ throw error;
}
}
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 f90ca4d72a..55799676bb 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,12 +3,14 @@ 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 { 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';
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
import { CreateWebhookSubscriptionForConnectedAccountCommand } from 'src/modules/connected-account/webhook-subscription-manager/commands/create-webhook-subscription-for-connected-account.command';
import { WebhookSubscriptionRenewalCronCommand } from 'src/modules/connected-account/webhook-subscription-manager/crons/commands/webhook-subscription-renewal.cron.command';
import { CreateWebhookSubscriptionJob } from 'src/modules/connected-account/webhook-subscription-manager/jobs/create-webhook-subscription.job';
+import { RenewWebhookSubscriptionJob } from 'src/modules/connected-account/webhook-subscription-manager/jobs/renew-webhook-subscription.job';
import { WebhookSubscriptionRenewalCronJob } from 'src/modules/connected-account/webhook-subscription-manager/crons/jobs/webhook-subscription-renewal.cron.job';
import { WebhookSubscriptionChannelDeletedListener } from 'src/modules/connected-account/webhook-subscription-manager/listeners/webhook-subscription-channel-deleted.listener';
import { CalendarWebhookSubscriptionService } from 'src/modules/connected-account/webhook-subscription-manager/services/calendar-webhook-subscription.service';
@@ -21,6 +23,7 @@ import { WebhookSubscriptionManagerModule } from 'src/modules/connected-account/
FeatureFlagModule,
WorkspaceIteratorModule,
TypeOrmModule.forFeature([
+ WorkspaceEntity,
ConnectedAccountEntity,
MessageChannelEntity,
CalendarChannelEntity,
@@ -33,11 +36,13 @@ import { WebhookSubscriptionManagerModule } from 'src/modules/connected-account/
WebhookSubscriptionRenewalCronJob,
WebhookSubscriptionRenewalCronCommand,
CreateWebhookSubscriptionJob,
+ RenewWebhookSubscriptionJob,
CreateWebhookSubscriptionForConnectedAccountCommand,
],
exports: [
MessagingWebhookSubscriptionService,
CalendarWebhookSubscriptionService,
+ WebhookSubscriptionRenewalCronCommand,
],
})
export class WebhookSubscriptionModule {}