[BUG] Chatwoot integration duplicates messages when a device re-delivers the same key.id — getExistingSourceIds exists but is never called on the live path

Open Beginner friendly
#2,675 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
postgresql, typescript
Domain
api, backend

Research direction

Start by reading ChatwootService.createMessage and compare its live path with ChatwootImport.getExistingSourceIds and importHistoryMessages. Trace how source_id and conversationId are passed, then verify that repeated WAID:<key.id> arrivals are ignored while lookup errors still allow delivery to continue. Confirm the existing callers handle the null result as described.

Written by the indexing model from the issue text.

Description

📋 Bug Description

When a sender's device re-delivers the same WhatsApp message (identical key.id, new timestamp — e.g. a buggy device or an unofficial client stuck in a loop resending an auto-reply), the Chatwoot integration creates a new Chatwoot message on every arrival. There is no idempotency check on the live path.

Real-world impact (production, v2.3.7): one guest's device looped an away-message roughly once a minute. In ~25h we accumulated 837 copies of a single message in one Chatwoot conversation (and a second looping key.id later reached 539 copies). The conversation became unusable for the support team. WhatsApp mobile shows the message once (native dedup by key.id); Chatwoot shows every copy.

The interesting part: the codebase already contains exactly the right checkChatwootImport.getExistingSourceIds(sourceIds, conversationId) queries Chatwoot's messages.source_id for WAID:<key.id> — but it is only called from the history import path (importHistoryMessages). The live path (ChatwootService.createMessage) posts to Chatwoot with source_id: WAID:<key.id> without consulting anything.

🔄 Steps to Reproduce

  1. Evolution API v2.3.7 (baileys channel) + Chatwoot integration enabled.
  2. From a sender device, deliver the same message twice with the same key.id (a looping unofficial client does this naturally; the events arrive with status: DELIVERY_ACK, source: 'unknown' and a fresh messageTimestamp each time).
  3. Watch the Chatwoot conversation.

✅ Expected Behavior

Second and subsequent arrivals of an already-imported key.id for the same conversation should be ignored (the same way importHistoryMessages already filters via getExistingSourceIds), or at least be configurable to be ignored.

❌ Actual Behavior

Every arrival creates a new Chatwoot message with the same source_id (WAID:<key.id>). messages.source_id in Chatwoot has a non-unique index, so nothing stops the duplicates downstream either.

💡 Suggested Fix

Call the existing check in the live path. We are running this in production (patched into createMessage) and it fully stopped the flood without affecting legitimate traffic:

// at the top of ChatwootService.createMessage(...), sourceId = "WAID:" + key.id
if (sourceId) {
  try {
    const existing = await chatwootImport.getExistingSourceIds([sourceId], conversationId);
    if (existing.has(sourceId)) {
      this.logger.warn(`[dedup] repeated message ignored source_id=${sourceId} conversation_id=${conversationId}`);
      return null; // callers already handle the null ("message not sent") path
    }
  } catch (e) {
    this.logger.warn(`[dedup] check unavailable: ${e}`); // fail open
  }
}

Notes:

  • getExistingSourceIds already accepts the optional conversationId argument, so the lookup is cheap (indexed on source_id).
  • Failing open on lookup errors keeps message delivery safe if the Chatwoot DB connection blips.
  • Verified in production: 14 duplicate arrivals blocked in the first minutes across 2 looping key.ids, zero legitimate messages affected.

🌍 Environment

  • Evolution API: v2.3.7 (image evoapicloud/evolution-api:v2.3.7)
  • Connection type: baileys
  • Chatwoot: v4.16.1, integration via CHATWOOT_ENABLED=true
  • DB: PostgreSQL (shared instance for Evolution + Chatwoot import connection)
  • OS: Linux (Docker)
Dominant language
TypeScript
Stars
9.6k
Forks
7.3k
PR merge metrics
No merged PRs in 30d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from evolution-foundation/evolution-api

All issues in evolution-foundation/evolution-api

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.