Skip to content

[BUG] Chatwoot integration duplicates messages when a device re-delivers the same key.id β€” getExistingSourceIds exists but is never called on the live pathΒ #2675

Description

@CantinhodaPraia

πŸ“‹ 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 check β€” ChatwootImport.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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions