From 2edd131a59ba71bb26624e1b1cea4cdac8763eb7 Mon Sep 17 00:00:00 2001 From: Wahyu Saputra Date: Wed, 3 Dec 2025 17:01:12 +0700 Subject: [PATCH 1/2] fix(chatwoot): dedupe automation echoes in messages.upsert --- CHANGELOG.md | 1 + .../whatsapp/whatsapp.baileys.service.ts | 36 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 790186755..d96c2103c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * [WIDGET-WORKS] Fix `PrismaClientValidationError` in `messages.update` (missing `Message` relation) by guarding `messageUpdate.create` calls. * [WIDGET-WORKS] Guard `messages.update` cache cleanup with a derived key and optional `MESSAGE_UPDATE_CACHE_DELETE_DISABLED` flag to prevent cache.delete crash-loops. * [WIDGET-WORKS] Save messages before Chatwoot sync in `/message/sendText` to avoid missing `chatwootMessageId` and log async sync failures. +* [WIDGET-WORKS] Skip Chatwoot sync in `messages.upsert` when a message key already has `chatwootMessageId` (handles Chatwoot automation echoes/duplicates). ### Features diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index ab1ecd0db..1b962c2f0 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1187,6 +1187,35 @@ export class BaileysStartupService extends ChannelStartupService { const messageRaw = this.prepareMessage(received); + const shouldSyncChatwoot = + this.configService.get('CHATWOOT').ENABLED && + this.localChatwoot?.enabled && + !received.key.id.includes('@broadcast'); + + if (shouldSyncChatwoot) { + const existingChatwootMessage = await this.prismaRepository.message.findFirst({ + where: { + key: { path: ['id'], equals: received.key.id }, + chatwootMessageId: { not: null }, + }, + select: { + chatwootMessageId: true, + chatwootInboxId: true, + chatwootConversationId: true, + }, + }); + + if (existingChatwootMessage) { + this.logger.verbose( + `[WIDGET-WORKS] Message ${received.key.id} already synced to Chatwoot (ID: ${existingChatwootMessage.chatwootMessageId}), skipping duplicate sync`, + ); + + messageRaw.chatwootMessageId = existingChatwootMessage.chatwootMessageId; + messageRaw.chatwootInboxId = existingChatwootMessage.chatwootInboxId; + messageRaw.chatwootConversationId = existingChatwootMessage.chatwootConversationId; + } + } + const isMedia = received?.message?.imageMessage || received?.message?.videoMessage || @@ -1204,11 +1233,8 @@ export class BaileysStartupService extends ChannelStartupService { await this.client.readMessages([received.key]); } - if ( - this.configService.get('CHATWOOT').ENABLED && - this.localChatwoot?.enabled && - !received.key.id.includes('@broadcast') - ) { + if (shouldSyncChatwoot && !messageRaw.chatwootMessageId) { + // [WIDGET-WORKS] Skip Chatwoot sync if this message was already synced (DB fallback after cache miss) const chatwootSentMessage = await this.chatwootService.eventWhatsapp( Events.MESSAGES_UPSERT, { instanceName: this.instance.name, instanceId: this.instanceId }, From 32d684caafdd416afd29c74a4f55e0f0b3b3f8aa Mon Sep 17 00:00:00 2001 From: Wahyu Saputra Date: Wed, 3 Dec 2025 18:02:21 +0700 Subject: [PATCH 2/2] Scope Chatwoot dedupe to instance --- .../integrations/channel/whatsapp/whatsapp.baileys.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 1b962c2f0..98832cc04 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1195,6 +1195,7 @@ export class BaileysStartupService extends ChannelStartupService { if (shouldSyncChatwoot) { const existingChatwootMessage = await this.prismaRepository.message.findFirst({ where: { + instanceId: this.instanceId, // [WIDGET-WORKS] Scope Chatwoot dedupe to current instance key: { path: ['id'], equals: received.key.id }, chatwootMessageId: { not: null }, },