Skip to content

Commit 9b6e70e

Browse files
committed
fix(chatwoot): resolve webhook timeout with fire-and-forget coordination
- Make autoPause coordination logic non-blocking (fire-and-forget) - Add conversation resolve handler: close paused bot sessions when conversation is resolved in Chatwoot - Prevents Chatwoot 5s webhook timeout causing messages to show as failed - Bot sessions are properly cleaned up on resolve, allowing new sessions
1 parent c70d9c3 commit 9b6e70e

1 file changed

Lines changed: 74 additions & 29 deletions

File tree

src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts

Lines changed: 74 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,35 @@ export class ChatwootService {
13251325
this.cache.delete(keyToDelete);
13261326
}
13271327

1328+
// Coordination: close paused bot sessions when conversation is resolved in Chatwoot
1329+
// This allows the bot to start a new session on the next incoming message
1330+
if (body.event === 'conversation_status_changed' && body.status === 'resolved' && body.meta?.sender?.identifier) {
1331+
const resolvedInstanceId = (this.waMonitor.waInstances[instance.instanceName])?.instanceId || instance.instanceId;
1332+
const resolvedChatId = body.meta.sender.identifier;
1333+
(async () => {
1334+
try {
1335+
const remoteJid = resolvedChatId.includes('@') ? resolvedChatId : `${resolvedChatId}@s.whatsapp.net`;
1336+
const closedSessions = await this.prismaRepository.integrationSession.updateMany({
1337+
where: {
1338+
instanceId: resolvedInstanceId,
1339+
remoteJid: remoteJid,
1340+
status: 'paused',
1341+
},
1342+
data: {
1343+
status: 'closed',
1344+
},
1345+
});
1346+
if (closedSessions.count > 0) {
1347+
this.logger.verbose(
1348+
`[Coordination] Closed ${closedSessions.count} paused bot session(s) for ${remoteJid} - conversation resolved in Chatwoot`,
1349+
);
1350+
}
1351+
} catch (error) {
1352+
this.logger.error(`[Coordination] Error closing paused sessions on resolve: ${error?.message}`);
1353+
}
1354+
})();
1355+
}
1356+
13281357
if (
13291358
!body?.conversation ||
13301359
body.private ||
@@ -1452,41 +1481,57 @@ export class ChatwootService {
14521481
}
14531482

14541483
// Coordination: pause active bot sessions when human agent responds from Chatwoot
1484+
// Fire-and-forget to avoid blocking the webhook response (Chatwoot has a 5s timeout)
14551485
// Respects autoPause config (global env var + per-instance override)
1456-
try {
1457-
let shouldAutoPause = true;
1486+
const coordInstanceId = instance.instanceId;
1487+
const coordChatId = chatId;
1488+
(async () => {
14581489
try {
1459-
// eslint-disable-next-line @typescript-eslint/no-var-requires
1460-
const { chatbotChatwootService } = require('@api/server.module');
1461-
if (chatbotChatwootService) {
1462-
const config = await chatbotChatwootService.getCoordinationConfig(instance.instanceId);
1463-
shouldAutoPause = config.autoPause;
1490+
let shouldAutoPause = true;
1491+
try {
1492+
// eslint-disable-next-line @typescript-eslint/no-var-requires
1493+
const { chatbotChatwootService } = require('@api/server.module');
1494+
if (chatbotChatwootService) {
1495+
const config = await chatbotChatwootService.getCoordinationConfig(coordInstanceId);
1496+
shouldAutoPause = config.autoPause;
1497+
}
1498+
} catch {
1499+
// If service not available, use default (true)
14641500
}
1465-
} catch {
1466-
// If service not available, use default (true)
1467-
}
14681501

1469-
if (shouldAutoPause) {
1470-
const remoteJidForSession = chatId.includes('@') ? chatId : `${chatId}@s.whatsapp.net`;
1471-
const pausedSessions = await this.prismaRepository.integrationSession.updateMany({
1472-
where: {
1473-
instanceId: instance.instanceId,
1474-
remoteJid: remoteJidForSession,
1475-
status: 'opened',
1476-
},
1477-
data: {
1478-
status: 'paused',
1479-
},
1480-
});
1481-
if (pausedSessions.count > 0) {
1482-
this.logger.verbose(
1483-
`[Coordination] Paused ${pausedSessions.count} bot session(s) for ${remoteJidForSession} - human agent responded from Chatwoot`,
1484-
);
1502+
if (shouldAutoPause) {
1503+
const remoteJidForSession = coordChatId.includes('@') ? coordChatId : `${coordChatId}@s.whatsapp.net`;
1504+
const pausedSessions = await this.prismaRepository.integrationSession.updateMany({
1505+
where: {
1506+
instanceId: coordInstanceId,
1507+
remoteJid: remoteJidForSession,
1508+
status: 'opened',
1509+
},
1510+
data: {
1511+
status: 'paused',
1512+
},
1513+
});
1514+
if (pausedSessions.count > 0) {
1515+
this.logger.verbose(
1516+
`[Coordination] Paused ${pausedSessions.count} bot session(s) for ${remoteJidForSession} - human agent responded from Chatwoot`,
1517+
);
1518+
1519+
// Open the Chatwoot conversation so the agent can handle it
1520+
try {
1521+
// eslint-disable-next-line @typescript-eslint/no-var-requires
1522+
const { chatbotChatwootService: coordService } = require('@api/server.module');
1523+
if (coordService) {
1524+
await coordService.updateChatwootConversationStatus(coordInstanceId, remoteJidForSession, 'open');
1525+
}
1526+
} catch (err) {
1527+
this.logger.error(`[Coordination] Error opening conversation after pause: ${err?.message}`);
1528+
}
1529+
}
14851530
}
1531+
} catch (error) {
1532+
this.logger.error(`[Coordination] Error pausing bot sessions: ${error?.message}`);
14861533
}
1487-
} catch (error) {
1488-
this.logger.error(`[Coordination] Error pausing bot sessions: ${error?.message}`);
1489-
}
1534+
})();
14901535

14911536
let formatText: string;
14921537
if (senderName === null || senderName === undefined) {

0 commit comments

Comments
 (0)