Skip to content

Conversation

@muriloleal13
Copy link
Contributor

@muriloleal13 muriloleal13 commented Nov 4, 2025

Fix: Incoming message events not working after reconnection

🐛 Problem Description

After disconnecting an instance (using /instance/logout) and reconnecting (using /instance/connect), incoming message events stop being triggered, while outgoing message events continue to work normally.

Affected Scenarios:

  • Restart instance (/instance/restart) - Works correctly
  • Logout + Connect (/instance/logout/instance/connect) - Broken
  • Automatic reconnection - Works correctly

User Impact:

  • Webhooks for incoming messages are not triggered after manual reconnection
  • Chatbot integrations (OpenAI, Dify, Typebot, etc.) stop receiving messages
  • Outgoing messages continue to work normally (misleading behavior)

🔍 Root Cause Analysis

The issue is in the BaileysMessageProcessor lifecycle management:

  1. Initial state: When an instance is created, messageProcessor.mount() is called in the constructor, creating an RxJS Subject and subscription
  2. Logout trigger: /instance/logoutlogoutInstance()messageProcessor.onDestroy()messageSubject.complete()
  3. Subject completed: Once complete() is called on an RxJS Subject, it becomes permanently closed and silently ignores all future next() calls
  4. Reconnection: /instance/connectconnectToWhatsapp()createClient() → event handlers are set up
  5. Messages arrive: Incoming messages trigger events['messages.upsert']messageProcessor.processMessage()messageSubject.next()SILENTLY IGNORED (Subject is completed)
  6. No webhooks: Since messages never flow through the processor, no webhooks/events are triggered

Why outgoing messages work:

Outgoing messages bypass the messageProcessor entirely and call sendDataWebhook() directly.

Why restart works:

The restartInstance() method closes the WebSocket and recreates the client WITHOUT calling logoutInstance(), so onDestroy() is never called and the messageProcessor remains functional.

✅ Solution

This PR implements a defensive approach with auto-recovery:

Changes Made:

1. baileysMessage.processor.ts - Add cleanup and auto-recovery in mount()

mount({ onMessageReceive }: MountProps) {
  // Cleanup old subscription to prevent memory leaks
  if (this.subscription && !this.subscription.closed) {
    this.subscription.unsubscribe();
  }

  // Auto-recovery: Recreate Subject if it was completed
  if (this.messageSubject.closed) {
    this.processorLogs.warn('MessageSubject was closed, recreating...');
    this.messageSubject = new Subject<{...}>();
  }
  
  // ... rest of the code
}

Benefits:

  • ✅ Prevents memory leaks from multiple subscriptions
  • ✅ Auto-recovers if Subject is completed (defensive programming)
  • ✅ Adds informative logging for debugging

2. whatsapp.baileys.service.ts - Remount processor on connection

public async connectToWhatsapp(number?: string): Promise<WASocket> {
  try {
    this.loadChatwoot();
    this.loadSettings();
    this.loadWebhook();
    this.loadProxy();

    // Remount messageProcessor to ensure subscription is active
    this.messageProcessor.mount({
      onMessageReceive: this.messageHandle['messages.upsert'].bind(this),
    });

    return await this.createClient(number);
  } catch (error) {
    this.logger.error(error);
    throw new InternalServerErrorException(error?.toString());
  }
}

Benefits:

  • ✅ Ensures subscription is active after every connection/reconnection
  • ✅ Works with the auto-recovery mechanism in mount()

🧪 Testing

Manual Testing Steps:

  1. Create an instance: POST /instance/create
  2. Connect: POST /instance/connect/{instanceName}
  3. Verify incoming messages trigger webhooks ✅
  4. Logout: DELETE /instance/logout/{instanceName}
  5. Reconnect: POST /instance/connect/{instanceName}
  6. Send a message to the instance
  7. Expected: Webhook is triggered ✅
  8. Before fix: Webhook is NOT triggered ❌

Tested Scenarios:

  • ✅ Create → Connect → Logout → Reconnect → Receive message
  • ✅ Multiple reconnections in sequence
  • ✅ Restart instance (ensure it still works)
  • ✅ Automatic reconnection after connection drop
  • ✅ Multiple instances in parallel

📊 Performance Impact

  • Memory: Negligible (Subject creation is ~0.1ms, properly garbage collected)
  • CPU: Minimal overhead from cleanup checks
  • Behavior: No breaking changes, only fixes broken functionality

🔄 Backward Compatibility

  • ✅ No breaking changes
  • ✅ No API changes
  • ✅ No configuration changes required
  • ✅ Works with all existing integrations (Baileys, Business API, Evolution)

📝 Additional Notes

  • This fix follows RxJS best practices (completed Subjects should be discarded and recreated)
  • The solution is defensive: even if complete() is called elsewhere in the future, the system auto-recovers
  • Logging was added to help debug similar issues in the future

🔗 Related Issues

This fix resolves the issue where users reported that after disconnecting and reconnecting instances, they stop receiving incoming message notifications/events, while outgoing messages continue to work normally.


Checklist:

  • Code follows project style guidelines (ESLint + Prettier)
  • Commit follows Conventional Commits format
  • Changes are backward compatible
  • Manual testing completed successfully
  • No breaking changes introduced

Summary by Sourcery

Fix incoming message events not being handled after manual logout and reconnect by adding cleanup and auto-recovery in the message processor and remounting it on each connection

Bug Fixes:

  • Unsubscribe stale RxJS subscriptions and recreate closed Subjects in BaileysMessageProcessor.mount to restore incoming message processing
  • Remount the message processor in connectToWhatsapp to reinitialize incoming message event handlers after reconnection

…nection

- Add cleanup logic in mount() to prevent memory leaks from multiple subscriptions

- Recreate messageSubject if it was completed during logout

- Remount messageProcessor in connectToWhatsapp() to ensure subscription is active after reconnection

This fixes the issue where incoming message events stop working after logout and reconnect, while outgoing message events continue to work normally.

The root cause was that onDestroy() calls complete() on the RxJS Subject, making it permanently closed. When reconnecting, the Subject would silently ignore all new messages.

The fix ensures that:

1. Old subscriptions are properly cleaned up before creating new ones

2. If the Subject is closed, a new one is created automatically

3. The messageProcessor is remounted on every connection to ensure active subscription
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Nov 4, 2025

Reviewer's Guide

This PR adds defensive lifecycle management to the BaileysMessageProcessor by cleaning up old subscriptions and auto-recovering a completed RxJS Subject in mount(), and ensures the processor is remounted on every manual reconnection in connectToWhatsapp().

Sequence diagram for incoming message event handling after reconnection

sequenceDiagram
participant Client
participant "BaileysStartupService"
participant "BaileysMessageProcessor"
participant "Webhook"

Client->>"BaileysStartupService": POST /instance/connect
"BaileysStartupService"->>"BaileysMessageProcessor": mount({ onMessageReceive })
"BaileysMessageProcessor"->>"BaileysMessageProcessor": Cleanup old subscription
"BaileysMessageProcessor"->>"BaileysMessageProcessor": Recreate Subject if completed
Client->>"BaileysStartupService": Incoming WhatsApp message
"BaileysStartupService"->>"BaileysMessageProcessor": processMessage()
"BaileysMessageProcessor"->>"Webhook": Trigger webhook for incoming message
Loading

Class diagram for updated BaileysMessageProcessor lifecycle

classDiagram
class BaileysMessageProcessor {
  - messageSubject: Subject
  - subscription: Subscription
  + mount(props: MountProps)
  + onDestroy()
}
BaileysMessageProcessor : mount() cleans up old subscription
BaileysMessageProcessor : mount() recreates Subject if completed
BaileysMessageProcessor : mount() sets up new subscription
Loading

Class diagram for BaileysStartupService changes

classDiagram
class BaileysStartupService {
  + connectToWhatsapp(number?: string): Promise<WASocket>
  - messageProcessor: BaileysMessageProcessor
  - messageHandle: any
}
BaileysStartupService : connectToWhatsapp() calls messageProcessor.mount() on every connection
Loading

File-Level Changes

Change Details Files
Add cleanup and auto-recovery logic in message processor mount
  • Unsubscribe existing subscription if still open to prevent memory leaks
  • Detect and recreate the Subject when it has been completed
  • Log a warning message when recreating the closed Subject
src/api/integrations/channel/whatsapp/baileysMessage.processor.ts
Remount the message processor on each manual reconnection
  • Invoke messageProcessor.mount() before creating a new WhatsApp client
  • Bind the incoming message handler to the upsert event
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Possibly linked issues

  • #Erros homolog 09/09/2025: The PR ensures incoming message events are processed after reconnection, which should prevent the decryption and processing errors described in the issue.

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:

  • Consider moving the messageProcessor.mount() call to after the Baileys socket emits its open event, so you don’t miss any messages that arrive during the initial client handshake.
  • Simplify the Subject lifecycle by always recreating messageSubject at the start of mount() (after unsubscribing) rather than conditionally checking closed, which makes the reset behavior more explicit.
  • Add a debug log when unsubscribing the old subscription to help trace cleanup operations and ensure no dangling subscriptions remain after reconnect.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider moving the messageProcessor.mount() call to after the Baileys socket emits its open event, so you don’t miss any messages that arrive during the initial client handshake.
- Simplify the Subject lifecycle by always recreating messageSubject at the start of mount() (after unsubscribing) rather than conditionally checking closed, which makes the reset behavior more explicit.
- Add a debug log when unsubscribing the old subscription to help trace cleanup operations and ensure no dangling subscriptions remain after reconnect.

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.

@GabrielSaMamba
Copy link

Aprovação necessária, o app está quebrando exatamente nessa parte

@Jeferson-Ramos-Einov
Copy link
Contributor

será que não seria melhor posicionar o this.messageProcessor.mount antes do this.messageProcessor.processMessage algo assim?

image

@DavidsonGomes DavidsonGomes changed the base branch from main to develop November 7, 2025 17:37
@DavidsonGomes DavidsonGomes merged commit 139ad9b into EvolutionAPI:develop Nov 7, 2025
4 of 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.

5 participants