fix(chatwoot): deliver bot QR when conversation is resolved#2579
fix(chatwoot): deliver bot QR when conversation is resolved#2579cesar-carlos wants to merge 1 commit into
Conversation
Reopen or create the bot inbox conversation before posting QR/status messages, since Chatwoot automations often resolve the init thread. Also tighten init command matching and send QR instructions in a single bot message. Co-authored-by: Cursor <cursoragent@cursor.com>
Reviewer's GuideReopens or creates a dedicated Chatwoot bot conversation before sending WhatsApp QR/status messages, hardens command parsing and webhook handling, and consolidates QR delivery into a single bot message. Sequence diagram for reopening or creating bot conversation before QR deliverysequenceDiagram
actor User
participant Chatwoot
participant ChatwootService
participant ChatwootClient
User->>Chatwoot: send /init
Chatwoot->>ChatwootService: receiveWebhook(body)
ChatwootService->>ChatwootService: isBotInitCommand(command)
alt [command is init]
ChatwootService->>ChatwootService: getOrOpenBotConversation(instance, inbox, contact)
alt [open conversation exists]
ChatwootService-->>ChatwootService: return openConversation
else [no open conversation]
ChatwootService->>ChatwootClient: contacts.listConversations(accountId, contactId)
ChatwootClient-->>ChatwootService: conversations
alt [latest inbox conversation is resolved]
ChatwootService->>ChatwootClient: conversations.toggleStatus(accountId, conversationId, status)
ChatwootClient-->>ChatwootService: updatedConversation
else [no conversation]
ChatwootService->>ChatwootClient: conversations.create(accountId, contact_id, inbox_id)
ChatwootClient-->>ChatwootService: createdConversation
end
end
ChatwootService->>ChatwootService: createBotQr(instance, msgQrCode, messageType, fileStream, filename)
end
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.
Hey - I've found 1 issue, and left some high level feedback:
- In
getOrOpenBotConversationyou re-querycontacts.listConversationseven thoughgetOpenConversationByContactalready fetched the conversations; consider refactoringgetOpenConversationByContactto optionally return all conversations (or accept a pre-fetched list) to avoid duplicate API calls on every bot message/QR. - The logic to derive
contactIdfromcontact.id || (contact as any)?.payload?.contact?.idis now duplicated in multiple methods; extracting a small helper (e.g.getContactId(contact)) would reduce repetition and keep the null-handling consistent.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `getOrOpenBotConversation` you re-query `contacts.listConversations` even though `getOpenConversationByContact` already fetched the conversations; consider refactoring `getOpenConversationByContact` to optionally return all conversations (or accept a pre-fetched list) to avoid duplicate API calls on every bot message/QR.
- The logic to derive `contactId` from `contact.id || (contact as any)?.payload?.contact?.id` is now duplicated in multiple methods; extracting a small helper (e.g. `getContactId(contact)`) would reduce repetition and keep the null-handling consistent.
## Individual Comments
### Comment 1
<location path="src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts" line_range="982-983" />
<code_context>
return null;
}
+ const contactId = contact.id || (contact as any)?.payload?.contact?.id;
+ if (!contactId) {
+ this.logger.warn('contact id not found');
+ return null;
</code_context>
<issue_to_address>
**suggestion:** Consider extracting contactId resolution into a small helper to avoid duplication.
This `contactId` resolution is duplicated with `getOrOpenBotConversation`. A shared helper (e.g. `getContactId(contact)`) would keep the behavior consistent and easier to update if the Chatwoot payload shape changes.
Suggested implementation:
```typescript
const contactId = this.getContactId(contact);
if (!contactId) {
this.logger.warn('contact id not found');
return null;
}
```
To fully implement the refactor and avoid duplication:
1. Inside the `ChatwootService` class, add a helper method (placement: near other private helpers):
```ts
private getContactId(contact: any): string | null {
const contactId = contact?.id || (contact as any)?.payload?.contact?.id;
return contactId ?? null;
}
```
2. In the `getOrOpenBotConversation` method (where the same `contactId` resolution is currently duplicated), replace the inline logic:
```ts
const contactId = contact.id || (contact as any)?.payload?.contact?.id;
```
with:
```ts
const contactId = this.getContactId(contact);
```
This will centralize the payload shape knowledge in one place and keep behavior consistent across usages.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const contactId = contact.id || (contact as any)?.payload?.contact?.id; | ||
| if (!contactId) { |
There was a problem hiding this comment.
suggestion: Consider extracting contactId resolution into a small helper to avoid duplication.
This contactId resolution is duplicated with getOrOpenBotConversation. A shared helper (e.g. getContactId(contact)) would keep the behavior consistent and easier to update if the Chatwoot payload shape changes.
Suggested implementation:
const contactId = this.getContactId(contact);
if (!contactId) {
this.logger.warn('contact id not found');
return null;
}To fully implement the refactor and avoid duplication:
- Inside the
ChatwootServiceclass, add a helper method (placement: near other private helpers):
private getContactId(contact: any): string | null {
const contactId = contact?.id || (contact as any)?.payload?.contact?.id;
return contactId ?? null;
}- In the
getOrOpenBotConversationmethod (where the samecontactIdresolution is currently duplicated), replace the inline logic:
const contactId = contact.id || (contact as any)?.payload?.contact?.id;with:
const contactId = this.getContactId(contact);This will centralize the payload shape knowledge in one place and keep behavior consistent across usages.
|
Related issue: #2580 |
|
please, do not send to main, you need to send to develop |
Summary
Fixes a production issue where WhatsApp QR codes are generated by Baileys but never reach the Chatwoot bot conversation after users send
init.Root cause:
createBotMessageandcreateBotQronly post to conversations withstatus === 'open'. Chatwoot automations frequently resolve the bot thread (+123456) right afterinit, so Evolution logsconversation not foundand silently drops the QR. Instances can remain stuck inconnectinguntilQRCODE_LIMITis reached.Changes
getOrOpenBotConversation()to reopen the latest bot inbox conversation or create a new one before sending bot messagescreateBotMessageandcreateBotQrinitcommands exactly (init,init:<number>,iniciar) instead ofincludes('init')receiveWebhookwhen the WA instance is not loaded in memoryReproduction (before fix)
CHATWOOT_BOT_CONTACT=trueinitin bot conversation (+123456)WARN conversation not found→ user never receives QR in ChatwootTest plan
initin bot conversation with a resolved thread → conversation reopens and QR image arrivesinitwhen no bot conversation exists → conversation is created and QR arrivesinit:5511999999999andiniciarstill trigger connectiondefinitionno longer trigger connectionQRCODE_LIMITis reachedMade with Cursor
Summary by Sourcery
Ensure Chatwoot bot conversations are reopened or created before delivering QR and status messages, and tighten handling of bot init commands and WhatsApp instance availability.
Bug Fixes:
Enhancements: