Skip to content

Conversation

@Vitordotpy
Copy link
Contributor

@Vitordotpy Vitordotpy commented Dec 16, 2025

📋 Description

This PR addresses critical issues with message handling and persistence caused by
Multi-Device (MD) JID formats in the WhatsApp integration (Baileys).

The core problem was that Baileys often emits message keys with device-specific
suffixes (e.g., :3 for web, :0 for phone), while the system and database expect
normalized JIDs (e.g., [email protected]). This mismatch caused:

  1. "Original message not found for update" warnings, as database lookups failed to
    match the "dirty" JID.
  2. Inconsistent message storage, where messages could be saved with or without the
    suffix depending on the flow.
  3. Prisma validation errors (Unknown argument fromMe) due to incorrect object
    structure in prepareMessage.

Changes:

  • Implemented robust JID normalization (replace(/:.*$/, '')) in messages.update
    handler to ensure database lookups and updates always use the canonical JID.
  • Applied the same normalization in prepareMessage (upsert flow) to guarantee new
    messages are stored cleanly.
  • Fixed a Prisma validation error by correctly nesting the fromMe property within
    the key object instead of the root level.

🔗 Related Issue

Closes # (insira o número da issue se houver)

🧪 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)

Verification Steps:

  1. Connected a WhatsApp Web session (device :3).
  2. Sent and received messages.
  3. Verified that messages.update events (status changes) no longer trigger
    "Original message not found" warnings in logs.
  4. Confirmed in the database that remoteJid and participant are stored without
    device suffixes.
  5. Verified successful message creation without Prisma errors.

📸 Screenshots (if applicable)

(Optional: Add screenshots of clean logs or DB records if available)

✅ 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

This fix is essential for stability in environments where users frequently switch
between devices (Phone/Web/Desktop), ensuring message history and status updates
remain consistent regardless of the source device.

Summary by Sourcery

Normalize WhatsApp Baileys message keys to use canonical JIDs and align stored messages with the expected schema.

Bug Fixes:

  • Strip device-specific suffixes from Baileys remoteJid and participant values before processing to ensure consistent database lookups and status updates.
  • Adjust prepared message payload structure so flags like fromMe are correctly nested under key, avoiding Prisma validation errors.

Enhancements:

  • Refactor message preparation to use normalized keys and a streamlined WAMessage-based structure, including standardized message metadata such as type, timestamp, and source device.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 16, 2025

Reviewer's Guide

Normalizes Baileys remoteJid/participant values to strip device suffixes before any DB interaction, and simplifies/strongly-types message preparation to avoid Prisma validation issues and ensure consistent message keys.

Sequence diagram for normalized message status update and DB lookup

sequenceDiagram
  participant BaileysClient
  participant BaileysStartupService
  participant MessagesUpdateHandler
  participant MessageRepository
  participant Database

  BaileysClient->>BaileysStartupService: messages.update(args)
  BaileysStartupService->>MessagesUpdateHandler: iterate args { key, update }
  MessagesUpdateHandler->>MessagesUpdateHandler: normalize keyAny.remoteJid
  MessagesUpdateHandler->>MessagesUpdateHandler: normalize keyAny.participant
  MessagesUpdateHandler->>MessageRepository: findMessageByKey(keyAny)
  MessageRepository->>Database: SELECT message WHERE remoteJid, participant
  Database-->>MessageRepository: normalized message row
  MessageRepository-->>MessagesUpdateHandler: message entity
  MessagesUpdateHandler->>MessagesUpdateHandler: build message object with normalized JIDs
  MessagesUpdateHandler->>MessageRepository: updateMessageStatus(message)
  MessageRepository->>Database: UPDATE status
  Database-->>MessageRepository: update ok
  MessageRepository-->>BaileysStartupService: status updated
  BaileysStartupService-->>BaileysClient: processing complete
Loading

Sequence diagram for prepareMessage normalization and persistence

sequenceDiagram
  participant BaileysClient
  participant BaileysStartupService
  participant prepareMessage
  participant MessageRepository
  participant Database

  BaileysClient->>BaileysStartupService: incoming WAMessage
  BaileysStartupService->>prepareMessage: prepareMessage(message)
  prepareMessage->>prepareMessage: keyAny = message.key as any
  prepareMessage->>prepareMessage: normalize keyAny.remoteJid
  prepareMessage->>prepareMessage: normalize keyAny.participant
  prepareMessage->>prepareMessage: build messageRaw with nested key and fromMe
  prepareMessage-->>BaileysStartupService: Message entity
  BaileysStartupService->>MessageRepository: upsertMessage(Message)
  MessageRepository->>Database: INSERT or UPDATE using normalized JIDs
  Database-->>MessageRepository: upsert ok
  MessageRepository-->>BaileysStartupService: persistence complete
Loading

ER diagram for Message storage with normalized JIDs

erDiagram
  INSTANCE {
    string id
    string name
  }

  MESSAGE {
    string id
    string remoteJid
    string participant
    string keyId
    boolean fromMe
    string status
    string instanceId
    string messageType
    number messageTimestamp
  }

  INSTANCE ||--o{ MESSAGE : has_messages
Loading

Class diagram for BaileysStartupService message key normalization

classDiagram
  class BaileysStartupService {
    +string instanceId
    +handleMessagesUpdate(args)
    -prepareMessage(message WAMessage) Message
    -deserializeMessageBuffers(input any) any
  }

  class WAMessage {
    +MessageKey key
    +string pushName
    +any message
    +number messageTimestamp
  }

  class MessageKey {
    +string id
    +string remoteJid
    +string participant
    +boolean fromMe
  }

  class Message {
    +MessageKey key
    +string pushName
    +any message
    +string messageType
    +number messageTimestamp
    +string source
    +string instanceId
  }

  class MessageRepository {
    +Message findMessageByKey(key MessageKey)
    +Message upsertMessage(message Message)
    +void updateMessageStatus(message Message)
  }

  BaileysStartupService --> WAMessage : consumes
  BaileysStartupService --> Message : produces
  WAMessage --> MessageKey : has
  Message --> MessageKey : has
  BaileysStartupService --> MessageRepository : uses
Loading

File-Level Changes

Change Details Files
Normalize JIDs early in message update handling to ensure consistent DB lookups and status updates.
  • Cast message key to a mutable any type (keyAny) and strip device suffixes from remoteJid using replace(/:.*$/, '')
  • Strip device suffixes from participant on the key object to keep it consistent with remoteJid normalization
  • Use the normalized keyAny.remoteJid when applying groupsIgnore filtering to avoid mismatches for group chats
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Ensure stored message records always use normalized JIDs for updates and status persistence.
  • Populate message.remoteJid using normalized keyAny.remoteJid with device suffix removed
  • Populate message.participant using normalized keyAny.participant with device suffix removed
  • Keep fromMe on the message object while relying on normalized identifiers for DB writes
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Refactor prepareMessage to normalize key JIDs, adjust typing, and fix Prisma validation structure for fromMe.
  • Change prepareMessage signature to use WAMessage as input and Message as return type for stronger typing
  • Rebuild key in messageRaw, copying message.key while normalizing remoteJid and participant via replace(/:.*$/, '')
  • Ensure fromMe remains nested under key as expected by Prisma, avoiding the previous root-level fromMe structure
  • Simplify messageRaw fields to rely on Baileys types directly (pushName, message, messageType, messageTimestamp) and derive source via getDevice(keyAny.id) using the normalized key
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

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

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • In the messages.update handler you cast key to any and mutate remoteJid/participant in place; consider normalizing into new local variables or a cloned key object instead of modifying the Baileys key directly to avoid side effects elsewhere that still expect the original JID format.
  • In prepareMessage you removed the deserializeMessageBuffers calls and the previous pushName/contextInfo/status handling; if those fields are still used downstream this could be a subtle regression, so it may be safer to preserve the previous deserialization and fallback logic while adding the JID normalization.
  • The new prepareMessage now assigns messageTimestamp directly from message.messageTimestamp without handling the Long case as before, which may change the stored type; consider keeping the explicit Long.isLong normalization to ensure a consistent numeric timestamp.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the messages.update handler you cast `key` to `any` and mutate `remoteJid`/`participant` in place; consider normalizing into new local variables or a cloned key object instead of modifying the Baileys key directly to avoid side effects elsewhere that still expect the original JID format.
- In `prepareMessage` you removed the `deserializeMessageBuffers` calls and the previous `pushName`/`contextInfo`/`status` handling; if those fields are still used downstream this could be a subtle regression, so it may be safer to preserve the previous deserialization and fallback logic while adding the JID normalization.
- The new `prepareMessage` now assigns `messageTimestamp` directly from `message.messageTimestamp` without handling the `Long` case as before, which may change the stored type; consider keeping the explicit `Long.isLong` normalization to ensure a consistent numeric timestamp.

## Individual Comments

### Comment 1
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:4665-4670` </location>
<code_context>
-  private prepareMessage(message: proto.IWebMessageInfo): any {
</code_context>

<issue_to_address>
**issue (bug_risk):** Changing `messageTimestamp` handling may introduce type/serialization issues when it is a Long.

Previously, `messageTimestamp` was always converted to a JS number when it was a `Long`:

```ts
messageTimestamp: Long.isLong(message.messageTimestamp)
  ? message.messageTimestamp.toNumber()
  : (message.messageTimestamp as number),
```

Now it’s passed through unchanged, so callers/DB serializers may receive a `Long` instead of a primitive number. If downstream code or schemas expect a numeric timestamp, this can break queries or JSON serialization. Please keep an explicit normalization (or equivalent guard) while adopting the new `WAMessage`/`Message` types.
</issue_to_address>

### Comment 2
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:4671-4678` </location>
<code_context>
-      pushName:
</code_context>

<issue_to_address>
**issue:** Dropping the pushName fallback logic may affect UX where pushName is absent.

Previously this fell back to `'Você'` for outgoing messages and to `message.participant`/the JID local-part when `message.pushName` was missing; now it relies solely on `message.pushName`. If that change wasn’t intentional, consider reintroducing a minimal fallback so UI/logs don’t show blank or `undefined` names.
</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.

@DavidsonGomes DavidsonGomes merged commit c7b7a99 into EvolutionAPI:develop Dec 16, 2025
5 checks passed
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