Open
Conversation
Contributor
Reviewer's GuideFixes two Meta WhatsApp Cloud API chatbot issues by allowing closed chatbot sessions to be recreated and by reordering Cloud API message handling to run Chatwoot first (and store correct Chatwoot IDs) before invoking webhooks and chatbot processing. Sequence diagram for updated Meta Cloud API message processing ordersequenceDiagram
actor WhatsAppUser
participant MetaCloudAPI
participant BusinessStartupService
participant ChatwootService
participant WebhookDispatcher
participant BaseChatbotController
WhatsAppUser->>MetaCloudAPI: send message
MetaCloudAPI->>BusinessStartupService: webhook payload
BusinessStartupService->>BusinessStartupService: build messageRaw
BusinessStartupService->>BusinessStartupService: sendTelemetry
Note over BusinessStartupService: Chatwoot first
BusinessStartupService->>ChatwootService: eventWhatsapp MESSAGES_UPSERT messageRaw
ChatwootService-->>BusinessStartupService: chatwootSentMessage
BusinessStartupService->>BusinessStartupService: set messageRaw.chatwootMessageId
BusinessStartupService->>BusinessStartupService: set messageRaw.chatwootInboxId from inbox_id
BusinessStartupService->>BusinessStartupService: set messageRaw.chatwootConversationId from conversation_id
BusinessStartupService->>WebhookDispatcher: sendDataWebhook MESSAGES_UPSERT messageRaw
BusinessStartupService->>BaseChatbotController: emit instance remoteJid msg pushName
BaseChatbotController-->>BusinessStartupService: chatbot processing complete
BusinessStartupService->>BusinessStartupService: persist messageRaw to database
Sequence diagram for chatbot emit with closed session recreationsequenceDiagram
participant BusinessStartupService
participant BaseChatbotController
participant SessionStore
participant GetConversationMessageUtil
participant BotRepository
BusinessStartupService->>BaseChatbotController: emit instance remoteJid msg pushName
BaseChatbotController->>SessionStore: getSession remoteJid instance
SessionStore-->>BaseChatbotController: session
BaseChatbotController->>GetConversationMessageUtil: getConversationMessage msg
GetConversationMessageUtil-->>BaseChatbotController: content or empty string
alt session exists and status is closed
BaseChatbotController->>BaseChatbotController: session = null
end
alt no session
BaseChatbotController->>BotRepository: findBotByTrigger botRepository content instanceId
BotRepository-->>BaseChatbotController: bot
BaseChatbotController->>BaseChatbotController: processBot with new session
else session not null and not closed
BaseChatbotController->>BaseChatbotController: processBot with existing session
end
BaseChatbotController-->>BusinessStartupService: emit complete
Class diagram for BaseChatbotController and related utilitiesclassDiagram
class BaseChatbotController {
<<abstract>>
+emit(instance remoteJid msg pushName) Promise~void~
+getSession(remoteJid instance) Promise~BotSession~
+processBot(instance remoteJid msg session content) Promise~void~
+checkIgnoreJids(ignoreJids remoteJid) boolean
}
class BotSession {
+status string
}
class GetConversationMessageUtil {
+getConversationMessage(msg) string
}
class FindBotByTriggerUtil {
+findBotByTrigger(botRepository content instanceId) Promise~BotType~
}
class BotRepository {
+findFirst(where) Promise~BotType~
}
BaseChatbotController --> BotSession : uses
BaseChatbotController --> GetConversationMessageUtil : uses
BaseChatbotController --> FindBotByTriggerUtil : uses
FindBotByTriggerUtil --> BotRepository : queries
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Contributor
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
findBotByTrigger, you now hit the repository twice fortriggerType in ['all','none']whencontentis falsy; consider extracting a small helper or reusing the same query so the “all/none” lookup is performed only once and its precedence is clearer. - The
content === ''guard infindBotByTriggerwon’t treat whitespace-only messages as empty; if those should behave like media-only/no-text messages, consider normalizing withcontent?.trim()before the checks. - Now that closed sessions are nulled in
BaseChatbotController.emit, review other call sites or session-related logic to see if this behavior should be centralized (e.g., insidegetSessionor a helper) so the semantics of closed sessions are consistent across the codebase.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `findBotByTrigger`, you now hit the repository twice for `triggerType in ['all','none']` when `content` is falsy; consider extracting a small helper or reusing the same query so the “all/none” lookup is performed only once and its precedence is clearer.
- The `content === ''` guard in `findBotByTrigger` won’t treat whitespace-only messages as empty; if those should behave like media-only/no-text messages, consider normalizing with `content?.trim()` before the checks.
- Now that closed sessions are nulled in `BaseChatbotController.emit`, review other call sites or session-related logic to see if this behavior should be centralized (e.g., inside `getSession` or a helper) so the semantics of closed sessions are consistent across the codebase.
## Individual Comments
### Comment 1
<location> `src/api/integrations/channel/meta/whatsapp.business.service.ts:679-683` </location>
<code_context>
if (chatwootSentMessage?.id) {
messageRaw.chatwootMessageId = chatwootSentMessage.id;
- messageRaw.chatwootInboxId = chatwootSentMessage.id;
- messageRaw.chatwootConversationId = chatwootSentMessage.id;
+ messageRaw.chatwootInboxId = chatwootSentMessage.inbox_id;
+ messageRaw.chatwootConversationId = chatwootSentMessage.conversation_id;
}
}
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Guard condition still depends on `id` even though inbox/conversation IDs now come from different fields.
Previously the guard on `chatwootSentMessage?.id` was equivalent for all three assignments because they shared the same value. Now `chatwootInboxId` and `chatwootConversationId` use different fields, a response with `inbox_id`/`conversation_id` but no `id` would be skipped. Unless the API guarantees `id` is always present with those fields, consider guarding on `chatwootSentMessage` or on the specific fields you’re assigning instead of `id`.
```suggestion
if (chatwootSentMessage) {
messageRaw.chatwootMessageId = chatwootSentMessage.id;
messageRaw.chatwootInboxId = chatwootSentMessage.inbox_id;
messageRaw.chatwootConversationId = chatwootSentMessage.conversation_id;
}
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
When a chatbot session exists with status='closed', the emit() method returned early, preventing the bot from re-activating on new messages. Root cause: the guard 'if (session.status === closed) return' was meant to skip sessions not awaiting user input, but it also prevented new conversations from starting after a bot flow completed. Fix: nullify the session instead of returning, so processBot enters the '!session' branch and creates a fresh session. Also adds null guards: - getConversationMessage: return empty string instead of undefined - findBotByTrigger: handle null/undefined content gracefully
Two bugs in BusinessStartupService message processing: 1. Execution order: Chatwoot was processed AFTER the bot emit(), but Baileys channel processes Chatwoot FIRST. This inconsistency meant the bot could not access chatwootConversationId/chatwootInboxId when processing messages from the Cloud API. 2. chatwootIds assignment: chatwootInboxId and chatwootConversationId were both incorrectly set to chatwootSentMessage.id instead of .inbox_id and .conversation_id respectively. Fix: reorder to Chatwoot-first (consistent with Baileys) and use the correct property names from the Chatwoot response object.
77b558a to
cb4a14d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR: fix(meta): Fix Cloud API chatbot auto-trigger and Chatwoot integration
📋 Description
Fixes two related bugs in the Meta WhatsApp Cloud API (
BusinessStartupService) that prevent chatbot integrations (Typebot, OpenAI, etc.) from working correctly when combined with Chatwoot.Bug 1: Closed bot sessions block new conversations
When a chatbot session completes (status =
closed), theemit()method inBaseChatbotControllerreturned early on any new message, permanently preventing the bot from re-activating for that contact.Root cause: The guard
if (session.status === 'closed') returnwas intended to skip sessions not awaiting input, but it also blocked the creation of new sessions after a flow completed.Fix: Nullify the closed session instead of returning, so
processBotenters the!sessionbranch and creates a fresh session. Also adds null guards ingetConversationMessage(returns''instead ofundefined) andfindBotByTrigger(handles null/empty content gracefully, only matchingall/nonetriggers).Files changed:
src/api/integrations/chatbot/base-chatbot.controller.tssrc/utils/getConversationMessage.tssrc/utils/findBotByTrigger.tsBug 2: Cloud API processes Chatwoot AFTER bot and assigns wrong IDs
In the Baileys channel, message processing follows the order: Chatwoot first → webhook → bot. But in the Meta Cloud API channel (
BusinessStartupService), the order was reversed: webhook → bot → Chatwoot. This inconsistency caused:chatwootConversationIdandchatwootInboxIdwere not yet available during bot processing.chatwootInboxIdandchatwootConversationIdwere incorrectly assignedchatwootSentMessage.id(the message ID) instead of.inbox_idand.conversation_idrespectively.Fix: Reorder to Chatwoot-first (consistent with Baileys) and use the correct property names from the Chatwoot API response.
Files changed:
src/api/integrations/channel/meta/whatsapp.business.service.ts🔗 Related Issue
This fixes chatbot integrations (Typebot, OpenAI, Dify, etc.) not auto-triggering on messages received via the Meta WhatsApp Cloud API, and incorrect Chatwoot metadata being persisted to the database.
🧪 Type of Change
🧪 Testing
Test scenarios verified:
inbox_idandconversation_idclosed), sending a new message correctly starts a fresh bot sessionall/nonetrigger bots without errors📸 Screenshots (if applicable)
N/A - Backend-only changes.
✅ Checklist
📝 Additional Notes
Changes summary (4 files, minimal diff)
base-chatbot.controller.tssession = nullinstead ofreturnwhen session is closed (+2 lines)getConversationMessage.tsreturn messageContent ?? ''(+1 char)findBotByTrigger.tsall/nonequery + early return for empty/whitespace content (+8 lines)whatsapp.business.service.ts.inbox_id/.conversation_id+ safer guard (+1/-0 net lines)Summary by Sourcery
Fix Meta WhatsApp Cloud API chatbot handling so bot sessions can restart correctly and Chatwoot metadata is processed consistently with other channels.
Bug Fixes:
Sourcery AI review feedback (addressed)
All suggestions from the automated review have been incorporated:
all/nonequery infindBotByTrigger→ Refactored to a single query at the top, with early return for empty content after the check.content?.trim() || ''to normalize before checks.chatwootSentMessage?.id→ Changed tochatwootSentMessagesinceinbox_idandconversation_idare now different fields.Why this matters
Without these fixes, any Evolution API deployment using the Meta WhatsApp Cloud API with chatbot integrations will experience:
chatwootInboxId/chatwootConversationIdstored in the database