Skip to content

fix(meta): improve webhook routing and support additional Cloud API events#2637

Open
vilsonei wants to merge 3 commits into
evolution-foundation:mainfrom
vilsonei:main
Open

fix(meta): improve webhook routing and support additional Cloud API events#2637
vilsonei wants to merge 3 commits into
evolution-foundation:mainfrom
vilsonei:main

Conversation

@vilsonei

@vilsonei vilsonei commented Jul 13, 2026

Copy link
Copy Markdown

📋 Description

🔗 Related Issue

Closes #(issue_number)

🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧹 Code cleanup
  • 🔒 Security fix

🧪 Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced
  • Tested with different connection types (if applicable)

📸 Screenshots (if applicable)

✅ Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have manually tested my changes thoroughly
  • I have verified the changes work with different scenarios
  • Any dependent changes have been merged and published

📝 Additional Notes

Summary by Sourcery

Improve Meta WhatsApp webhook processing to robustly route events to the correct instances, safely handle diverse Cloud API payloads, and enhance logging and contact resolution in the business startup service.

Bug Fixes:

  • Correct webhook handling to support multiple Meta WhatsApp instances for the same phone number and avoid failures when instances are not loaded.
  • Handle missing or malformed webhook payloads more safely, including absent content, template IDs, phone numbers, and contact data.
  • Fix phone number resolution in WhatsApp connection logic to consider messages, statuses, and message echoes, reducing connection errors.

Enhancements:

  • Improve webhook routing for Meta WhatsApp by iterating over all entries and changes and invoking each matching instance separately.
  • Add structured logging around template webhooks and instance processing to aid debugging of Meta webhook flows.
  • Refine contact handling in WhatsApp business service to derive remote JID from either profile phone or wa_id and set push name more defensively.

vilsonei added 3 commits July 13, 2026 12:20
…e 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.
@sourcery-ai

sourcery-ai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactors Meta webhook handling to properly route template status and message events across multiple instances, adds defensive checks and logging, and expands WhatsApp Business startup logic to support additional Cloud API payload shapes.

Sequence diagram for Meta webhook routing across instances

sequenceDiagram
  actor MetaWebhook
  participant MetaController
  participant TemplateRepository
  participant InstanceRepository
  participant TemplateWebhookEndpoint
  participant BusinessStartupService

  MetaWebhook->>MetaController: receiveWebhook(data)
  alt [change.field === message_template_status_update]
    MetaController->>TemplateRepository: template.findFirst(templateId)
    TemplateRepository-->>MetaController: template
    MetaController->>TemplateWebhookEndpoint: axios.post(change.value)
  else [message event]
    MetaController->>InstanceRepository: instance.findMany(numberId)
    InstanceRepository-->>MetaController: instances
    loop for each instance
      MetaController->>BusinessStartupService: connectToWhatsapp(webhookData)
    end
  end
  MetaController-->>MetaWebhook: { status: success }
Loading

File-Level Changes

Change Details Files
Refactor Meta webhook processing to iterate over entries/changes, separate template status updates from message events, and route notifications to multiple instances with better error handling.
  • Early-return for non-whatsapp_business_account webhooks
  • Iterate over data.entry and each entry.changes instead of hard-coded first element access
  • Handle message_template_status_update separately by fetching template, validating webhookUrl, and posting the change value
  • Switch instance lookup to findMany by phone_number_id and process all matching instances
  • Build a narrowed webhookData containing only the current entry/change before passing to WhatsApp instances
  • Use Promise.allSettled to connect all instances, logging per-instance success or failure
src/api/integrations/channel/meta/meta.controller.ts
Harden WhatsApp Business startup webhook handling to support messages, statuses, and message_echoes payloads and improve error reporting.
  • Add null-safety when reading content from data.entry[0].changes[0].value and log when missing
  • Derive phone number from messages.from, statuses.recipient_id, or message_echoes.to in priority order, with error logging if none are present
  • Set this.phoneNumber using the derived phone number and then load Chatwoot and invoke eventHandler asynchronously
  • Normalize InternalServerErrorException to use the error message string rather than toString
src/api/integrations/channel/meta/whatsapp.business.service.ts
Adjust contact extraction for received WhatsApp events to use a more robust remoteJid and pushName.
  • Extract receivedContact once and derive remoteJid from contact.profile.phone or fallback to contact.wa_id
  • Set pushName from contact.profile.name or default to empty string
  • Reuse remoteJid when constructing contactRaw objects instead of directly indexing into contacts
src/api/integrations/channel/meta/whatsapp.business.service.ts

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In BusinessStartupService.connectToWhatsapp, receivedContact = received.contacts[0] is accessed before checking that received.contacts exists, which can throw when contacts are missing; consider guarding that access or using optional chaining.
  • The new error handling in connectToWhatsapp wraps errors in InternalServerErrorException with only the message, which drops stack and original error context; consider including the original error or logging more structured details to aid debugging.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `BusinessStartupService.connectToWhatsapp`, `receivedContact = received.contacts[0]` is accessed before checking that `received.contacts` exists, which can throw when contacts are missing; consider guarding that access or using optional chaining.
- The new error handling in `connectToWhatsapp` wraps errors in `InternalServerErrorException` with only the message, which drops stack and original error context; consider including the original error or logging more structured details to aid debugging.

## Individual Comments

### Comment 1
<location path="src/api/integrations/channel/meta/whatsapp.business.service.ts" line_range="411-414" />
<code_context>
       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) {
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard against missing or empty `received.contacts` before indexing into it.

`receivedContact = received.contacts[0]` runs before the `if (received.contacts)` guard, so an undefined or empty `contacts` array will cause a runtime error. Consider guarding first, e.g.:

```ts
const receivedContact = Array.isArray(received.contacts) ? received.contacts[0] : undefined;
if (receivedContact) {
  const remoteJid = receivedContact.profile?.phone || receivedContact.wa_id;
  pushName = receivedContact.profile?.name || '';
}
```

This prevents crashes when `contacts` is missing or empty and keeps `remoteJid` aligned with the same presence checks as `pushName`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +411 to +414
const receivedContact = received.contacts[0];
const remoteJid = receivedContact.profile?.phone || receivedContact.wa_id;

if (received.contacts) pushName = receivedContact.profile?.name || '';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Guard against missing or empty received.contacts before indexing into it.

receivedContact = received.contacts[0] runs before the if (received.contacts) guard, so an undefined or empty contacts array will cause a runtime error. Consider guarding first, e.g.:

const receivedContact = Array.isArray(received.contacts) ? received.contacts[0] : undefined;
if (receivedContact) {
  const remoteJid = receivedContact.profile?.phone || receivedContact.wa_id;
  pushName = receivedContact.profile?.name || '';
}

This prevents crashes when contacts is missing or empty and keeps remoteJid aligned with the same presence checks as pushName.

@dpaes

dpaes commented Jul 13, 2026

Copy link
Copy Markdown

what is the purpose of this PR? @vilsonei
we dont accept anything to main, only to develop.

@vilsonei

Copy link
Copy Markdown
Author

what is the purpose of this PR? @vilsonei we dont accept anything to main, only to develop.
@dpaes

Thanks for the clarification. I'll retarget the PR to the develop branch.

The purpose of this PR is to fix a few bugs I found while using the Meta Official (WhatsApp Cloud API) integration.

The first issue occurs when the sender's WhatsApp account does not have a profile name. In this case, Evolution API throws an exception and stops processing the webhook, so the event is never forwarded to the instance webhook.

The second issue affects scenarios where multiple Evolution instances are linked to the same WABA phone_number_id. When Meta sends a webhook for that phone number, Evolution only forwards the event to the first matching instance, ignoring all the others. As a result, only one instance receives the Meta webhook, even though all instances associated with the same phone_number_id should receive it.

While working on these fixes, I also added support for message_echoes events and improved the webhook handling to make it more resilient to different payload formats sent by the Meta Cloud API.

@dpaes

dpaes commented Jul 14, 2026

Copy link
Copy Markdown

@gomessguii @pastoriniMatheus
a good improvement (needs review)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants