-
Notifications
You must be signed in to change notification settings - Fork 5.2k
fix: chatwoot stability, webhook crashes and WhatsApp LID support #2274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: chatwoot stability, webhook crashes and WhatsApp LID support #2274
Conversation
Reviewer's GuideStrengthens Chatwoot, Typebot, and WhatsApp Baileys integrations by preserving full JIDs (including @lid), hardening Chatwoot contact lookup and import flows against 422/database failures, and preventing webhook crashes from invalid participant IDs. Sequence diagram for Chatwoot contact creation with 422 fallback by identifiersequenceDiagram
actor BotEngine
participant ChatwootService
participant ChatwootAPI
BotEngine->>ChatwootService: createContact(instance, jid)
ChatwootService->>ChatwootAPI: POST contacts (identifier = jid)
alt contact created successfully
ChatwootAPI-->>ChatwootService: 201 Created contact
ChatwootService->>ChatwootAPI: POST addLabelToContact(inbox, contactId)
ChatwootService-->>BotEngine: contact
else 422 identifier has already been taken
ChatwootAPI-->>ChatwootService: 422 Unprocessable Entity
ChatwootService->>ChatwootService: check error.status and error.message
ChatwootService->>ChatwootService: findContactByIdentifier(instance, jid)
ChatwootService->>ChatwootAPI: GET contacts/search q=jid
alt contact found in search payload
ChatwootAPI-->>ChatwootService: contact.data.payload[0]
ChatwootService->>ChatwootAPI: POST addLabelToContact(inbox, contactId)
ChatwootService-->>BotEngine: existingContact
else search empty
ChatwootAPI-->>ChatwootService: empty result
ChatwootService->>ChatwootAPI: POST contacts/filter identifier == jid
alt contact found in filter payload
ChatwootAPI-->>ChatwootService: contact.payload[0]
ChatwootService->>ChatwootAPI: POST addLabelToContact(inbox, contactId)
ChatwootService-->>BotEngine: existingContact
else still not found
ChatwootAPI-->>ChatwootService: empty result
ChatwootService-->>BotEngine: null
end
end
else other error
ChatwootAPI-->>ChatwootService: error
ChatwootService->>ChatwootService: logger.error Error creating contact
ChatwootService-->>BotEngine: null
end
Sequence diagram for WhatsApp GROUP_PARTICIPANTS_UPDATE normalizationsequenceDiagram
participant WhatsAppWebhook
participant BaileysStartupService
participant WebhookConsumer
WhatsAppWebhook-->>BaileysStartupService: GROUP_PARTICIPANTS_UPDATE body
BaileysStartupService->>BaileysStartupService: for each participantId
BaileysStartupService->>BaileysStartupService: normalizePhoneNumber(id)
BaileysStartupService->>BaileysStartupService: String(id || '').split('@')[0]
BaileysStartupService-->>WebhookConsumer: sanitizedParticipantId
WebhookConsumer->>WebhookConsumer: process update without crash
Class diagram for updated chatbot and WhatsApp services JID handlingclassDiagram
class BaseChatbotService {
<<abstract>>
+sendMedia(instance, remoteJid, mediaType, url, altText, settings) void
+sendText(instance, remoteJid, message, linkPreview, settings) void
}
class TypebotService {
+processListMessage(instance, formattedText, remoteJid) void
+processButtonMessage(instance, formattedText, remoteJid) void
}
class ChatwootService {
+createContact(instance, jid) any
+findContact(instance, phoneNumber) any
+findContactByIdentifier(instance, identifier) any
+getNumberFromRemoteJid(remoteJid) string
+startImportHistoryMessages(instance) void
+handleGroupMessage(body) void
}
class BaileysStartupService {
+handleGroupParticipantsUpdate(body) void
+normalizePhoneNumber(id) string
}
BaseChatbotService <|-- TypebotService
class ChatwootImport {
+updateMessageSourceID(messageId, sourceId) Promise~void~
}
class InstanceDto
ChatwootService --> InstanceDto
ChatwootService --> ChatwootImport
TypebotService --> InstanceDto
BaileysStartupService --> InstanceDto
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- The new
findContactByIdentifiermethod contains several near-duplicate response-shape checks (contact.data.payload,contact.payload,contactByAttr.payload,contactByAttr.data.payload); consider centralizing this into a small helper that normalizes a Chatwoot response to a singlepayloadarray to reduce branching and future maintenance overhead. - In
getNumberFromRemoteJid, the function now guards against falsy input but the signature still declaresremoteJid: string; it would be clearer to either widen the type (e.g.,string | null | undefined) or move the null/undefined handling to the call sites so the type matches actual expectations. - The repeated inline logic for deriving
rawPhoneNumberfrombody.keyin ChatwootService (checkingaddressingMode === 'lid',fromMe,participantAlt, etc.) would be easier to reason about and less error-prone if factored into a shared helper that encapsulates the JID-to-phone extraction rules in one place.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new `findContactByIdentifier` method contains several near-duplicate response-shape checks (`contact.data.payload`, `contact.payload`, `contactByAttr.payload`, `contactByAttr.data.payload`); consider centralizing this into a small helper that normalizes a Chatwoot response to a single `payload` array to reduce branching and future maintenance overhead.
- In `getNumberFromRemoteJid`, the function now guards against falsy input but the signature still declares `remoteJid: string`; it would be clearer to either widen the type (e.g., `string | null | undefined`) or move the null/undefined handling to the call sites so the type matches actual expectations.
- The repeated inline logic for deriving `rawPhoneNumber` from `body.key` in ChatwootService (checking `addressingMode === 'lid'`, `fromMe`, `participantAlt`, etc.) would be easier to reason about and less error-prone if factored into a shared helper that encapsulates the JID-to-phone extraction rules in one place.
## Individual Comments
### Comment 1
<location> `src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts:349-351` </location>
<code_context>
return contact;
} catch (error) {
+ if (
+ (error.status === 422 || error.response?.status === 422) &&
+ (error.message?.includes('taken') || error.response?.data?.message?.includes('taken')) &&
+ jid
+ ) {
</code_context>
<issue_to_address>
**issue (bug_risk):** Relying on `'taken'` substring in the error message is brittle and may misclassify errors.
This 422 path depends on `error.message?.includes('taken')` / `error.response?.data?.message?.includes('taken')`, which tightly couples behavior to a particular English string from Chatwoot. That can silently break with localization, wording changes, or different 422 variants. Prefer branching on a structured signal from Chatwoot (e.g., error code, attribute, or a specific payload field). If that’s not available, at least narrow this condition (e.g., to cases where the failing field is the identifier/email) so unrelated 422s containing the word `taken` don’t incorrectly hit this path.
</issue_to_address>
### Comment 2
<location> `src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts:2534-2538` </location>
<code_context>
}
public getNumberFromRemoteJid(remoteJid: string) {
- return remoteJid.replace(/:\d+/, '').split('@')[0];
+ if (!remoteJid) {
+ return '';
+ }
+ return remoteJid.replace(/:\d+/, '').replace('@s.whatsapp.net', '').replace('@g.us', '').replace('@lid', '');
}
</code_context>
<issue_to_address>
**issue:** The new `getNumberFromRemoteJid` implementation may fail to strip unknown JID suffixes and changes behavior for some inputs.
The previous `.split('@')[0]` stripped any suffix; the new version only handles three specific ones, so JIDs like `@broadcast` or future/custom domains will now include the suffix in the "number", which may break callers expecting a pure phone number. You could keep the generic stripping while still handling the numeric resource explicitly, e.g.:
```ts
if (!remoteJid) return '';
const cleaned = remoteJid.replace(/:\d+/, '');
return cleaned.split('@')[0];
```
or apply your specific replacements first and then still `split('@')[0]` as a final safeguard. Also, returning `''` instead of `undefined`/`null` for falsy `remoteJid` changes the function’s contract, so it’s worth checking existing callers for assumptions about that value.
</issue_to_address>
### Comment 3
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:1616-1620` </location>
<code_context>
// Helper to normalize participantId as phone number
- const normalizePhoneNumber = (id: string): string => {
+ const normalizePhoneNumber = (id: string | any): string => {
// Remove @lid, @s.whatsapp.net suffixes and extract just the number part
- return id.split('@')[0];
+ return String(id || '').split('@')[0];
};
</code_context>
<issue_to_address>
**suggestion:** The `normalizePhoneNumber` parameter type `string | any` is misleading and undermines type safety.
Since the implementation already does `String(id || '')`, the function is designed to handle non-string/nullable inputs. Typing the parameter as `string | any` collapses to `any` and removes useful type checking. Use a more accurate type such as `id: string | null | undefined` (or `unknown` if you truly expect arbitrary values) so call sites are still type-checked while your runtime fallback remains valid.
```suggestion
// Helper to normalize participantId as phone number
const normalizePhoneNumber = (id: string | null | undefined): string => {
// Remove @lid, @s.whatsapp.net suffixes and extract just the number part
return String(id || '').split('@')[0];
};
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts
Outdated
Show resolved
Hide resolved
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts
Outdated
Show resolved
Hide resolved
|
Criei uma nova PR com mais fixes além desses, vou fechar essa daqui |
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.
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.
Summary by Sourcery
Improve system stability and Chatwoot integration reliability by identifier handling, and error resilience.
Bug Fixes:
Summary by Sourcery
Improve WhatsApp and Chatwoot integrations to better handle modern JID formats, avoid crashes, and gracefully recover from external and database errors.
Bug Fixes:
Enhancements: