Skip to content

Conversation

@josuecocoman
Copy link

@josuecocoman josuecocoman commented Feb 8, 2026

📋 Description

Add GET /label/syncLabels/:instanceName endpoint that forces WhatsApp to re-download labels via Baileys' resyncAppState(['regular'], true).

Problem

Currently, GET /label/findLabels/:instanceName only reads labels from the Prisma database. Labels created, renamed, or recolored on WhatsApp after the initial connection are never reflected in the API response. There is no way to force a re-sync of labels from WhatsApp.

Solution

New endpoint syncLabels that:

  1. Calls this.client.resyncAppState(['regular'], true) to trigger an incremental re-download of WhatsApp's label app state
  2. Waits 3 seconds for the existing LABELS_EDIT and LABELS_ASSOCIATION event handlers to process incoming data
  3. Returns the updated labels from the database (same LabelDto[] format as findLabels)

Key Technical Details

  • resyncAppState(['regular'], true) — the true parameter means "incremental" (only fetch changes since last sync). This is safe and does NOT cause disconnects.
  • resyncAppState(['regular'], false) — would download the FULL snapshot, which causes WhatsApp to disconnect the session. This was tested and confirmed in production. We intentionally use true.
  • 3-second delay allows Baileys' built-in event handlers (labelHandle[Events.LABELS_EDIT] and labelHandle[Events.LABELS_ASSOCIATION]) to process and persist the incoming label data before reading from DB.
  • No schema validation needed — like findLabels, this is a simple GET with only the instanceName parameter.

Use Case

CRM integrations (Odoo, Chatwoot, etc.) that need to keep label data in sync with WhatsApp. The CRM calls syncLabels periodically to ensure it has the latest labels before mapping them to contacts/conversations.

Files Changed

File Change
whatsapp.baileys.service.ts Added syncLabels() method using resyncAppState + fetchLabels()
label.controller.ts Added syncLabels() delegation (same pattern as fetchLabels)
label.router.ts Added GET /label/syncLabels/:instanceName route (same pattern as findLabels)
evolution.channel.service.ts Added stub throwing BadRequestException
whatsapp.business.service.ts Added stub throwing BadRequestException

🔗 Related Issue

This addresses a gap in the label management API. Related to #1904 (labels.association handling).

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

Test results (production VPS, Evolution API v2.2.0):

  • GET /label/syncLabels/testInstance → HTTP 200, returns updated LabelDto[]
  • ✅ No WhatsApp disconnection (incremental sync with isLatest=true)
  • ✅ Labels created after initial connection are now returned after sync
  • ✅ Existing findLabels and handleLabel endpoints completely unaffected
  • ✅ Evolution Channel stub correctly returns 400 BadRequestException
  • ✅ WhatsApp Business API stub correctly returns 400 BadRequestException
  • ✅ Tested integration with Odoo CRM — labels synced successfully

✅ 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
  • 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

  • The resyncAppState function is provided by Baileys but was never used anywhere in the Evolution API codebase before this PR.
  • This feature is particularly valuable for multi-device setups where labels are managed from the phone but need to be reflected in the API.
  • The 3-second delay is a pragmatic choice based on production testing. It could be made configurable in a future enhancement.

Summary by Sourcery

Add support for explicitly re-syncing WhatsApp labels and expose it via a new HTTP endpoint.

New Features:

  • Introduce a Baileys service method to trigger an incremental WhatsApp label resync and return updated labels.
  • Expose a new GET /label/syncLabels/:instanceName route and controller method to trigger label resync for a given instance.

Chores:

  • Add stub syncLabels methods for Evolution and WhatsApp Business channels that return BadRequest exceptions.

Delegates to service.syncLabels() via waMonitor, following the
same pattern as fetchLabels and handleLabel.
New endpoint that forces WhatsApp to re-download labels via
Baileys resyncAppState(['regular'], true) before returning
updated labels from DB. Uses same dataValidate pattern as findLabels.
Uses Baileys client.resyncAppState(['regular'], true) to force
an incremental re-download of WhatsApp app state (labels).
Waits 3s for LABELS_EDIT/LABELS_ASSOCIATION event handlers to
process, then returns updated labels from DB via fetchLabels().

Key: isLatest=true means incremental (safe, no disconnect).
isLatest=false would download full snapshot and cause disconnection.
Throws BadRequestException as syncLabels is only available
for Baileys (WhatsApp Web) connections.
Throws BadRequestException as syncLabels is only available
for Baileys (WhatsApp Web) connections.
Copilot AI review requested due to automatic review settings February 8, 2026 21:29
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 8, 2026

Reviewer's Guide

Adds a new GET /label/syncLabels/:instanceName endpoint that triggers a Baileys incremental app state resync for labels, waits for label events to be processed, and then returns the refreshed labels from the database, while stubbing the same method as unsupported on non-Baileys channels.

Sequence diagram for GET /label/syncLabels endpoint flow

sequenceDiagram
  actor CRM
  participant ApiServer
  participant LabelRouter
  participant LabelController
  participant WaMonitor
  participant BaileysStartupService
  participant BaileysClient
  participant LabelEventHandlers
  participant Database

  CRM->>ApiServer: HTTP GET /label/syncLabels/:instanceName
  ApiServer->>LabelRouter: route syncLabels
  LabelRouter->>LabelRouter: dataValidate request to LabelDto
  LabelRouter->>LabelController: syncLabels(InstanceDto)
  LabelController->>WaMonitor: waInstances[instanceName]
  WaMonitor->>BaileysStartupService: syncLabels()

  BaileysStartupService->>BaileysClient: resyncAppState([regular], true)
  BaileysClient-->>BaileysStartupService: resync initiated

  BaileysClient->>LabelEventHandlers: emit LABELS_EDIT events
  BaileysClient->>LabelEventHandlers: emit LABELS_ASSOCIATION events
  LabelEventHandlers->>Database: upsert label records

  BaileysStartupService->>BaileysStartupService: wait 3000 ms
  BaileysStartupService->>Database: fetchLabels()
  Database-->>BaileysStartupService: LabelDto[]

  BaileysStartupService-->>WaMonitor: LabelDto[]
  WaMonitor-->>LabelController: LabelDto[]
  LabelController-->>LabelRouter: LabelDto[]
  LabelRouter-->>ApiServer: HTTP 200 LabelDto[]
  ApiServer-->>CRM: HTTP 200 LabelDto[]
Loading

Updated class diagram for label sync across channel services

classDiagram

  class ChannelStartupService {
  }

  class BaileysStartupService {
    +fetchLabels() Promise~LabelDto[]~
    +syncLabels() Promise~LabelDto[]~
    +handleLabel(data HandleLabelDto) Promise~any~
  }

  class EvolutionStartupService {
    +fetchLabels() Promise~void~
    +syncLabels() Promise~void~
    +handleLabel() Promise~void~
  }

  class BusinessStartupService {
    +fetchLabels() Promise~void~
    +syncLabels() Promise~void~
    +handleLabel() Promise~void~
  }

  class LabelController {
    +fetchLabels(instance InstanceDto) Promise~LabelDto[]~
    +handleLabel(instance InstanceDto, data HandleLabelDto) Promise~any~
    +syncLabels(instance InstanceDto) Promise~LabelDto[]~
  }

  class LabelRouter {
    +router Router
    +registerRoutes() void
  }

  class WaMonitor {
    +waInstances map~string, ChannelStartupService~
  }

  ChannelStartupService <|-- BaileysStartupService
  ChannelStartupService <|-- EvolutionStartupService
  ChannelStartupService <|-- BusinessStartupService

  WaMonitor "1" --> "*" ChannelStartupService : waInstances

  LabelController --> WaMonitor : uses
  LabelRouter --> LabelController : uses
Loading

File-Level Changes

Change Details Files
Implement Baileys-based label resync flow and expose it via controller and router as GET /label/syncLabels/:instanceName.
  • Add BaileysStartupService.syncLabels() to call client.resyncAppState(['regular'], true), wait 3 seconds, and then delegate to fetchLabels() to read updated labels from the DB.
  • Expose syncLabels() on LabelController, delegating to the underlying waInstance for the given instanceName.
  • Register GET /label/syncLabels/:instanceName route in LabelRouter using the existing dataValidate pattern and LabelDto response type.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
src/api/controllers/label.controller.ts
src/api/routes/label.router.ts
Add syncLabels stubs for non-Baileys WhatsApp channels to keep the ChannelStartupService interface consistent while explicitly marking the operation as unsupported.
  • Add syncLabels() to EvolutionStartupService that throws BadRequestException with a clear error message.
  • Add syncLabels() to BusinessStartupService that throws BadRequestException with a clear error message.
src/api/integrations/channel/evolution/evolution.channel.service.ts
src/api/integrations/channel/meta/whatsapp.business.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 - I've left some high level feedback:

  • The hard-coded 3-second timeout in syncLabels may be brittle under varying load/network conditions; consider making this delay configurable or driven by a more deterministic signal that label events have been processed.
  • In syncLabels, you currently assume this.client.resyncAppState always succeeds; consider wrapping the call and the wait in a try/catch and returning a clearer error if the resync fails or times out.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The hard-coded 3-second timeout in `syncLabels` may be brittle under varying load/network conditions; consider making this delay configurable or driven by a more deterministic signal that label events have been processed.
- In `syncLabels`, you currently assume `this.client.resyncAppState` always succeeds; consider wrapping the call and the wait in a try/catch and returning a clearer error if the resync fails or times out.

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.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an API endpoint to force a WhatsApp label re-sync (via Baileys app-state resync) so that labels changed on-device after the initial connection are reflected in the Prisma-backed findLabels response.

Changes:

  • Added GET /label/syncLabels/:instanceName route and controller wiring.
  • Implemented Baileys syncLabels() using resyncAppState(['regular'], true) + delayed DB read.
  • Added syncLabels() stubs for non-Baileys channel implementations (Business API / Evolution Channel).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/api/routes/label.router.ts Registers syncLabels route alongside existing label endpoints
src/api/controllers/label.controller.ts Adds controller delegation to instance syncLabels()
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts Implements Baileys label re-sync + returns updated labels from DB
src/api/integrations/channel/evolution/evolution.channel.service.ts Adds syncLabels() stub throwing BadRequestException
src/api/integrations/channel/meta/whatsapp.business.service.ts Adds syncLabels() stub throwing BadRequestException

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

ClassRef: LabelDto,
execute: (instance) => labelController.syncLabels(instance),
});

Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

GET /label/syncLabels/:instanceName triggers a server-side re-sync with WhatsApp (side effects + external I/O). Using GET for a mutating/side-effecting operation breaks HTTP semantics and increases the risk of accidental invocation via caching/prefetching. Consider changing this to POST (or at least add Cache-Control: no-store / disable caching) to make the intent explicit and safer for intermediaries.

Suggested change
res.set('Cache-Control', 'no-store');

Copilot uses AI. Check for mistakes.
@@ -1,4 +1,4 @@
import { getCollectionsDto } from '@api/dto/business.dto';
import { getCollectionsDto } from '@api/dto/business.dto';
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

The first import line contains a UTF-8 BOM/zero-width character (shown as an invisible character before import). This can cause noisy diffs and occasional tooling/lint issues. Please remove the BOM so the file starts with a plain import ....

Suggested change
import { getCollectionsDto } from '@api/dto/business.dto';
import { getCollectionsDto } from '@api/dto/business.dto';

Copilot uses AI. Check for mistakes.
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.

1 participant