evolution-api icon indicating copy to clipboard operation
evolution-api copied to clipboard

Fix: @lid problems, messages events and chatwoot integration errors

Open Vitordotpy opened this issue 1 month ago • 2 comments

This Pull Request resolves critical stability issues and functional bugs in the Evolution API, focusing on Chatwoot integration robustness and support for modern WhatsApp identifiers (LID). The fixes ensure reliable message processing, prevent application crashes due to webhook errors, and normalize identifier handling across services, the fixes also ensure that messages update, delete and poll events work fine as good.

Fixes:

WhatsApp LID (Linked Identity Device) Support: Problem: Messages originating from users with the new @lid identifier format were failing in Typebot and general message sending. The codebase aggressively stripped the domain using .split('@')[0], causing the API to lose the valid JID required for these specific identifiers. Solution: Updated BaseChatbotService, TypebotService, and ChatwootService logic to preserve the full JID when necessary. Refactored getNumberFromRemoteJid to safely handle and clean complex JID formats (e.g., removing device suffixes like :27 while keeping the user ID intact).

Chatwoot Integration Stability (Duplicate Identifiers & Participant Errors): Problem: The Chatwoot integration suffered from two main issues: crashes due to participantAlt being undefined when parsing message keys, and "Identifier has already been taken" (422) errors during contact creation when a contact existed but wasn't initially found. Solution: Added safe access checks for participantAlt to prevent runtime errors. Implemented a fallback mechanism in createContact to recover the existing contact by identifier if creation fails due to duplication, ensuring the flow continues without error.

Webhook Crashes on Group Participant Updates: Problem: The GROUP_PARTICIPANTS_UPDATE webhook event caused the application to crash when processing participant IDs that were not strings (undefined or null) in the normalizePhoneNumber function. Solution: Added strict type coercion (String(id || '')) in WhatsappBaileysService to ensure input is always a string before processing, preventing the crash.

Unhandled Database Rejections in Chatwoot Import: Problem: The chatwoot_import feature caused unhandled promise rejections crashing the process when the separate Postgres connection was missing or failed. Solution: Wrapped the updateMessageSourceID and connection logic in proper try/catch blocks to gracefully handle database connectivity issues without bringing down the main application.

Messages update and delete handle Problem: A wrong verifying logic was stopping api to send webhook events of deleted, updated and poll votes. Solution: Enhanced and changed, cached message, update message content and poll vote verifications, now the cached message verification uses the message timestamp to ignore it, wich makes the webhook to send poll vote, message update and message delete events. The poll message type verification that makes poll update not work was removed.

Summary by Sourcery

Improve system stability and Chatwoot integration reliability by identifier handling, and error resilience.

Bug Fixes:

Enable full support for WhatsApp @lid identifiers in Typebot and Chatbot services by refining JID parsing. Fix participantAlt undefined errors and "Identifier has already been taken" loops in Chatwoot integration. Prevent GROUP_PARTICIPANTS_UPDATE webhook crashes by sanitizing participant ID inputs. Handle database connection failures gracefully in Chatwoot import services to prevent unhandled rejections. Summary by Sourcery Improve WhatsApp and Chatwoot integrations to better handle modern JID formats, avoid crashes, and gracefully recover from external and database errors. Restored message update, poll and message delete events to work again.

Enhancements:

Support WhatsApp LID and other JID formats end‑to‑end by preserving full remoteJid where necessary and normalizing only the domain/suffix parts. Add a Chatwoot helper to search contacts by identifier across multiple API shapes for more robust contact resolution.

Summary by Sourcery

Improve WhatsApp and Chatwoot integrations to better handle modern identifiers, prevent crashes, and restore message lifecycle webhooks.

Bug Fixes:

  • Recover existing Chatwoot contacts when creation fails with 422 errors instead of failing the flow.
  • Avoid runtime errors when resolving group participants by checking for participantAlt before accessing it.
  • Prevent crashes on GROUP_PARTICIPANTS_UPDATE webhooks by safely normalizing potentially non-string participant IDs.
  • Restore delivery/read, update, delete, and poll vote webhooks by refining message cache checks, status defaults, and update filtering logic.
  • Avoid failures when Chatwoot import message source ID updates throw by wrapping them in error handling.

Enhancements:

  • Support WhatsApp LID and other JID formats end-to-end by preserving full remoteJid in chatbot and Typebot services instead of stripping domains.
  • Expose a Chatwoot helper to search contacts by identifier across multiple API endpoints and response shapes for more robust contact resolution.
  • Improve duplicate message update detection by caching and comparing timestamps rather than simple presence flags.

Vitordotpy avatar Nov 29 '25 00:11 Vitordotpy

Reviewer's Guide

Refactors WhatsApp identifier handling to support @lid JIDs end‑to‑end, hardens Chatwoot integration and imports against 422s and DB failures, and fixes webhook/update handling logic so updates, deletes, and participant events no longer crash or get dropped.

Sequence diagram for Chatwoot contact creation with 422 fallback

sequenceDiagram
  participant WS as BaileysStartupService
  participant CS as ChatwootService
  participant CW as ChatwootAPI

  WS->>CS: createContact(instance, jid)
  CS->>CW: POST contacts (identifier = jid)
  CW-->>CS: 422 Unprocessable Entity (identifier taken)
  alt status 422 and jid defined
    CS->>CS: logger.warn("creation failed (422)...")
    CS->>CS: findContactByIdentifier(instance, jid)
    CS->>CW: GET contacts/search?q=jid
    CW-->>CS: existing contact payload or empty
    alt contact found in search
      CS->>CS: addLabelToContact(nameInbox, contactId)
      CS-->>WS: existingContact
    else not found in search
      CS->>CW: POST contacts/filter (attribute_key = identifier)
      CW-->>CS: contactByAttr payload or empty
      alt contact found by attribute
        CS-->>WS: contactByAttr[0]
      else contact not found anywhere
        CS->>CS: logger.error("Error creating contact")
        CS-->>WS: null
      end
    end
  else non-422 error
    CS->>CS: logger.error("Error creating contact")
    CS-->>WS: null
  end

Sequence diagram for chatbot media/text sending with preserved remoteJid

sequenceDiagram
  actor U as EndUser
  participant WA as WhatsApp
  participant BSvc as BaseChatbotService
  participant TSvc as TypebotService
  participant Inst as InstanceAdapter

  U-->>WA: sends message to bot (may use @lid JID)
  WA-->>TSvc: webhook with session.remoteJid

  alt Typebot sends image/video/audio
    TSvc->>Inst: mediaMessage or audioWhatsapp
      activate TSvc
      TSvc-->>Inst: payload { number = session.remoteJid, ... }
      deactivate TSvc
  end

  alt Typebot sends list or buttons
    TSvc->>Inst: processListMessage or processButtonMessage
    TSvc->>Inst: payload { number = remoteJid, ... }
  end

  alt BaseChatbotService sends generic media
    BSvc->>Inst: audioWhatsapp or mediaMessage
    BSvc-->>Inst: payload { number = remoteJid, ... }
  end

  alt BaseChatbotService sends text
    BSvc->>Inst: textMessage
    BSvc-->>Inst: payload { number = remoteJid, ... }
  end

  Inst-->>WA: outgoing WhatsApp message
  WA-->>U: bot response delivered

Class diagram for updated identifier handling and Chatwoot integration

classDiagram
  class ChatwootService {
    +createContact(instance: InstanceDto, jid: string) Promise<any>
    +findContactByIdentifier(instance: InstanceDto, identifier: string) Promise<any>
    +findContact(instance: InstanceDto, phoneNumber: string) Promise<any>
    +getNumberFromRemoteJid(remoteJid: string) string
    +handleUpsertMessage(instance: InstanceDto, key: any, body: any) Promise<void>
    +handleImportHistory(instance: InstanceDto, key: any, chatwootMessageIds: any) Promise<void>
  }

  class BaileysStartupService {
    +handleMessagesUpsert(upsert: any, type: string) Promise<void>
    +handleMessagesUpdate(update: any, key: any) Promise<void>
    +normalizePhoneNumber(id: string~or~any) string
    -baileysCache: BaileysCache
    -instanceId: string
    -instance: any
    -configService: ConfigService
    -localChatwoot: any
  }

  class BaseChatbotService {
    <<abstract>>
    +sendMediaMessage(instance: any, remoteJid: string, mediaType: string, url: string, altText: string, settings: any) Promise<void>
    +sendTextMessage(instance: any, remoteJid: string, message: string, linkPreview: boolean, settings: any) Promise<void>
  }

  class TypebotService {
    +processListMessage(instance: any, formattedText: string, remoteJid: string) Promise<void>
    +processButtonMessage(instance: any, formattedText: string, remoteJid: string) Promise<void>
    +handleImageMessage(instance: any, session: any, message: any, settings: any) Promise<void>
    +handleVideoMessage(instance: any, session: any, message: any, settings: any) Promise<void>
    +handleAudioMessage(instance: any, session: any, message: any, settings: any) Promise<void>
  }

  class ChatwootImport {
    +updateMessageSourceID(chatwootMessageId: string, sourceId: string) Promise<void>
  }

  class BaileysCache {
    +get(key: string) Promise<any>
    +set(key: string, value: any, ttlSeconds: number) Promise<void>
  }

  class ConfigService {
    +get~Chatwoot~(key: string) Chatwoot
  }

  class Chatwoot {
    +ENABLED: boolean
  }

  ChatwootService --> ChatwootImport : uses
  ChatwootService --> ConfigService : uses
  BaileysStartupService --> BaileysCache : uses
  BaileysStartupService --> ConfigService : uses
  TypebotService --|> BaseChatbotService

File-Level Changes

Change Details Files
Improve Chatwoot contact creation robustness and add identifier-based contact lookup helpers.
  • On 422 errors during contact creation, attempt to find an existing contact by the same identifier and reuse it, including reapplying labels.
  • Introduce a findContactByIdentifier helper that searches Chatwoot contacts via search endpoint and attribute filter, handling multiple possible response shapes.
  • Ensure chatwoot_import.updateMessageSourceID is awaited and wrapped in try/catch to avoid crashing on DB connectivity or import failures.
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts
Harden group participant handling and WhatsApp JID normalization for webhooks and Chatwoot integration, including LID support.
  • Guard access to body.key.participantAlt so it is only used when present, preventing undefined access crashes for group messages.
  • Update getNumberFromRemoteJid to return early for falsy input and to preserve full JIDs that contain '@lid', only stripping device suffixes and domains for non-LID JIDs.
  • Adjust GROUP_PARTICIPANTS_UPDATE normalization helper to coerce potentially null/undefined participant IDs to strings before splitting, preventing runtime errors.
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Fix Baileys message/update webhook filtering and caching so updates, deletions, and polls are emitted correctly without deadlocks.
  • Relax the incoming message filter to stop dropping poll update messages by removing the explicit pollUpdateMessage check from the early-continue condition.
  • Invert the update.message null check so only non-message status updates are skipped instead of all message-carrying updates.
  • Change the Baileys cache value from a boolean to the messageTimestamp and only treat an update as duplicate when timestamps match, reducing false positives.
  • Adjust default status mapping from DELETED to SERVER_ACK when an unknown Baileys status is encountered in webhook payloads.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Propagate full remoteJid (including @lid) through chatbot services instead of stripping domain parts.
  • Update BaseChatbotService media and text sending helpers to pass the full remoteJid to instance methods instead of splitting on '@'.
  • Update TypebotService media, list, and button message builders to use the full remoteJid as the target number, aligning with LID-compatible downstream APIs.
src/api/integrations/chatbot/base-chatbot.service.ts
src/api/integrations/chatbot/typebot/services/typebot.service.ts

Possibly linked issues

  • #[Bug] Typebot fails to send messages to users with LID - BadRequestException jidOptions.exists false: The PR updates BaseChatbot/Typebot to preserve full remoteJid, correctly supporting LID users and fixing the reported bug.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an issue from a review comment by replying to it. You can also reply to a review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull request title to generate a title at any time. You can also comment @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in the pull request body to generate a PR summary at any time exactly where you want it. You can also comment @sourcery-ai summary on the pull request to (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the pull request to resolve all Sourcery comments. Useful if you've already addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull request to dismiss all existing Sourcery reviews. Especially useful if you want to start fresh with a new review - don't forget to comment @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

  • Contact our support team for questions or feedback.
  • Visit our documentation for detailed guides and information.
  • Keep in touch with the Sourcery team by following us on X/Twitter, LinkedIn or GitHub.

sourcery-ai[bot] avatar Nov 29 '25 00:11 sourcery-ai[bot]

top, tenho visto relamações aumentando sobre msgs não aparecendo no chatwoot devido essas alterações do identificador lid

claudiocastelo avatar Nov 29 '25 13:11 claudiocastelo