From 02298134d2a44f8ac1065dae43167ddf9decca73 Mon Sep 17 00:00:00 2001 From: Vilsonei Machado Date: Mon, 13 Jul 2026 12:20:38 -0300 Subject: [PATCH 1/3] fix(meta): prevent TypeError when contact name is missing --- .../channel/meta/whatsapp.business.service.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/api/integrations/channel/meta/whatsapp.business.service.ts b/src/api/integrations/channel/meta/whatsapp.business.service.ts index 1e4808c156..f1c67cf88e 100644 --- a/src/api/integrations/channel/meta/whatsapp.business.service.ts +++ b/src/api/integrations/channel/meta/whatsapp.business.service.ts @@ -387,7 +387,10 @@ export class BusinessStartupService extends ChannelStartupService { let messageRaw: any; let pushName: any; - if (received.contacts) pushName = received.contacts[0].profile.name; + const receivedContact = received.contacts[0]; + const remoteJid = receivedContact.profile?.phone || receivedContact.wa_id; + + if (received.contacts) pushName = receivedContact.profile?.name || ''; if (received.messages) { const message = received.messages[0]; // Añadir esta línea para definir message @@ -702,7 +705,7 @@ export class BusinessStartupService extends ChannelStartupService { }); const contactRaw: any = { - remoteJid: received.contacts[0].profile.phone, + remoteJid, pushName, // profilePicUrl: '', instanceId: this.instanceId, @@ -714,7 +717,7 @@ export class BusinessStartupService extends ChannelStartupService { if (contact) { const contactRaw: any = { - remoteJid: received.contacts[0].profile.phone, + remoteJid, pushName, // profilePicUrl: '', instanceId: this.instanceId, From 694dd65233982fcd560f25249f91b05f9d7669a2 Mon Sep 17 00:00:00 2001 From: Vilsonei Machado Date: Mon, 13 Jul 2026 12:35:55 -0300 Subject: [PATCH 2/3] fix(meta): dispatch webhooks to all instances linked to the same phone number Replace findFirst() with findMany() to support multiple instances per phone number. Replace async forEach() with safe asynchronous iteration. Dispatch webhooks concurrently using Promise.allSettled(). Ensure one instance failure does not prevent delivery to the remaining instances. Add validation and logging for missing or unloaded instances. Process each webhook change independently before dispatching. --- .../channel/meta/meta.controller.ts | 132 +++++++++++++----- 1 file changed, 98 insertions(+), 34 deletions(-) diff --git a/src/api/integrations/channel/meta/meta.controller.ts b/src/api/integrations/channel/meta/meta.controller.ts index 558a22e98b..a886f5a315 100644 --- a/src/api/integrations/channel/meta/meta.controller.ts +++ b/src/api/integrations/channel/meta/meta.controller.ts @@ -15,54 +15,118 @@ export class MetaController extends ChannelController implements ChannelControll integrationEnabled: boolean; public async receiveWebhook(data: any) { - if (data.object === 'whatsapp_business_account') { - if (data.entry[0]?.changes[0]?.field === 'message_template_status_update') { - const template = await this.prismaRepository.template.findFirst({ - where: { templateId: `${data.entry[0].changes[0].value.message_template_id}` }, - }); + if (data.object !== 'whatsapp_business_account') { + return { + status: 'success', + }; + } - if (!template) { - console.log('template not found'); - return; - } + const entries = data.entry ?? []; - const { webhookUrl } = template; + for (const entry of entries) { + const changes = entry.changes ?? []; - await axios.post(webhookUrl, data.entry[0].changes[0].value, { - headers: { - 'Content-Type': 'application/json', - }, - }); - return; - } + for (const change of changes) { + if (change?.field === 'message_template_status_update') { + const templateId = change?.value?.message_template_id; + + if (!templateId) { + this.logger.error('WebhookService -> receiveWebhookMeta -> templateId not found'); + continue; + } + + const template = await this.prismaRepository.template.findFirst({ + where: { + templateId: String(templateId), + }, + }); + + if (!template) { + this.logger.error(`WebhookService -> receiveWebhookMeta -> template not found: ${templateId}`); + continue; + } + + if (!template.webhookUrl) { + this.logger.error(`WebhookService -> receiveWebhookMeta -> template webhookUrl not found: ${templateId}`); + continue; + } + + try { + await axios.post(template.webhookUrl, change.value, { + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + this.logger.error( + `WebhookService -> receiveWebhookMeta -> error sending template webhook: ${ + error instanceof Error ? error.message : String(error) + }`, + ); + } + + continue; + } - data.entry?.forEach(async (entry: any) => { - const numberId = entry.changes[0].value.metadata.phone_number_id; + const numberId = change?.value?.metadata?.phone_number_id; if (!numberId) { this.logger.error('WebhookService -> receiveWebhookMeta -> numberId not found'); - return { - status: 'success', - }; + continue; } - const instance = await this.prismaRepository.instance.findFirst({ - where: { number: numberId }, + const instances = await this.prismaRepository.instance.findMany({ + where: { + number: String(numberId), + }, }); - if (!instance) { - this.logger.error('WebhookService -> receiveWebhookMeta -> instance not found'); - return { - status: 'success', - }; + if (!instances.length) { + this.logger.error(`WebhookService -> receiveWebhookMeta -> instances not found for numberId: ${numberId}`); + continue; } - await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data); - - return { - status: 'success', + const webhookData = { + ...data, + entry: [ + { + ...entry, + changes: [change], + }, + ], }; - }); + + const results = await Promise.allSettled( + instances.map(async (instance) => { + const waInstance = this.waMonitor.waInstances[instance.name]; + + if (!waInstance) { + throw new Error(`Instance not loaded: ${instance.name}`); + } + + await waInstance.connectToWhatsapp(webhookData); + + return instance.name; + }), + ); + + results.forEach((result, index) => { + const instanceName = instances[index].name; + + if (result.status === 'rejected') { + this.logger.error( + `WebhookService -> receiveWebhookMeta -> error processing webhook for instance ${instanceName}: ${ + result.reason instanceof Error ? result.reason.message : String(result.reason) + }`, + ); + return; + } + + this.logger.log( + `WebhookService -> receiveWebhookMeta -> webhook processed successfully for instance ${instanceName}`, + ); + }); + } } return { From 1cb137a88066845e5ac9cefa9e54256b248679c1 Mon Sep 17 00:00:00 2001 From: Vilsonei Machado Date: Mon, 13 Jul 2026 14:09:44 -0300 Subject: [PATCH 3/3] fix(meta): handle message echoes when resolving webhook phone number --- .../channel/meta/whatsapp.business.service.ts | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/api/integrations/channel/meta/whatsapp.business.service.ts b/src/api/integrations/channel/meta/whatsapp.business.service.ts index f1c67cf88e..95eec55e16 100644 --- a/src/api/integrations/channel/meta/whatsapp.business.service.ts +++ b/src/api/integrations/channel/meta/whatsapp.business.service.ts @@ -126,17 +126,38 @@ export class BusinessStartupService extends ChannelStartupService { public async connectToWhatsapp(data?: any): Promise { if (!data) return; - const content = data.entry[0].changes[0].value; + const content = data?.entry?.[0]?.changes?.[0]?.value; + + if (!content) { + this.logger.error('ChannelStartupService -> connectToWhatsapp -> webhook content not found'); + return; + } try { - this.loadChatwoot(); + const message = Array.isArray(content.messages) ? content.messages[0] : undefined; + + const status = Array.isArray(content.statuses) ? content.statuses[0] : undefined; + + const messageEcho = Array.isArray(content.message_echoes) ? content.message_echoes[0] : undefined; + + const phoneNumber = message?.from ?? status?.recipient_id ?? messageEcho?.to; - this.eventHandler(content); + if (!phoneNumber) { + this.logger.error( + 'ChannelStartupService -> connectToWhatsapp -> phone number not found in messages, statuses or message_echoes', + ); + return; + } + + this.phoneNumber = createJid(phoneNumber); - this.phoneNumber = createJid(content.messages ? content.messages[0].from : content.statuses[0]?.recipient_id); + this.loadChatwoot(); + + await this.eventHandler(content); } catch (error) { this.logger.error(error); - throw new InternalServerErrorException(error?.toString()); + + throw new InternalServerErrorException(error instanceof Error ? error.message : String(error)); } }