Skip to content

chatwoot-adapter: no initial history sync + phone/linked-device messages never relayed to Chatwoot #615

Description

@unaisshemim

Summary

The chatwoot-adapter only relays live inbound WhatsApp messages (message:received, fromMe === false). This causes two major sync gaps in real deployments:

  1. First-time setup: Existing WhatsApp conversations do not appear in Chatwoot when the adapter is enabled — only messages that arrive after enable are relayed.
  2. Linked phone / Apple device: Messages the operator sends from their physical phone (or other linked WhatsApp clients) are never relayed to Chatwoot, even when the customer replies and a live conversation is ongoing.

Operators expect a helpdesk bridge to show the full conversation thread, not just post-enable inbound traffic from contacts.

Related: #609 (parity meta), #606 (reply context), #607 (voice), #608 (media).


Problem 1 — No initial / historical sync on first connect

Expected

When enabling chatwoot-adapter (or connecting a WhatsApp session for the first time), existing chats and recent message history should be imported into the mapped Chatwoot API inbox — similar to connecting any helpdesk channel.

Actual

Chatwoot stays empty (or missing most contacts) until new inbound messages arrive after enable.

Root cause

The adapter registers only the message:received hook:

ctx.registerHook("message:received", async (h) => { ... handleInbound ... });

There is no handler for:

  • session:ready (backfill on connect)
  • onHistoryMessages / history sync bulk events
  • Existing OpenWA /messages/.../history API

OpenWA itself documents that history bulk is persist-only, no dispatch:

onHistoryMessages: (messages): void => {
  // Persist for the chat view only; no dispatch (these predate the live session).
  void this.persistHistoryMessages(id, messages)...
},

So history exists in OpenWA's DB/dashboard but never reaches the Chatwoot adapter.

Real-world workaround

Deployments must run a separate custom script (e.g. sync-inbound.sh) that:

  • Calls OpenWA chats + history APIs manually
  • Creates Chatwoot contacts/conversations
  • Posts text-only messages (no media, no source_id, no reply threading)
  • Seeds plugin mapping files by hand

This should be a built-in adapter feature (configurable: syncOnEnable, historyLimit, unread-only vs full).


Problem 2 — Phone / linked-device messages not synced to Chatwoot

Expected

If an agent chats with a customer on their iPhone / WhatsApp mobile app (linked device), those messages should appear in the Chatwoot conversation thread — both the operator's outgoing texts and the customer's replies.

Actual

  • Customer replies from phone may appear (if they arrive as live message:received)
  • Operator messages composed on the phone never appear in Chatwoot
  • The Chatwoot thread looks one-sided / broken

Root cause (adapter)

Inbound filter explicitly drops all fromMe messages:

export function shouldRelayInbound(msg, source, relayGroups) {
  return source === 'Engine' && !msg.fromMe && !!msg.chatId && ...
}

The adapter does not subscribe to message:sent.

Root cause (OpenWA engine — important context)

OpenWA correctly routes phone-composed sends through a different event path than live inbound:

// whatsapp-web-js adapter
this.client.on('message_create', msg => {
  // message_create fires for messages composed on a linked phone
  if (!msg.fromMe) return;
  this.callbacks.onMessageCreate?.(...);
});

// session.service.ts
onMessageCreate: (message) => {
  if (!message.fromMe) return;
  void this.hookManager.execute('message:sent', messageData, { sessionId: id, source: 'Engine' });
  // NOTE: intentionally does NOT persist phone-composed sends to messages table (yet)
  void this.webhookService.dispatch(id, 'message.sent', finalMessage);
}

So phone sends emit message:sent, but chatwoot-adapter ignores that hook entirely.

Additionally, OpenWA does not persist phone-composed outgoing messages to its own messages table (only webhooks/WS) — so even the OpenWA dashboard chat view can be incomplete for phone sends. Chatwoot sync is impossible without handling message:sent.

Reproduction

  1. Enable chatwoot-adapter, map a WhatsApp session to a Chatwoot inbox.
  2. On your linked iPhone, open a 1:1 chat and send messages to a contact (do not use Chatwoot or OpenWA API).
  3. Have the contact reply.
  4. Open the conversation in Chatwoot.

Result: Contact replies may show; your phone-sent messages do not.


Proposed fix

A. Initial sync (on enable / session ready)

On session:ready (or first message:received per chat), optionally:

  1. Fetch recent chats from OpenWA (/chats or engine store)
  2. For each chat (respect relayGroups), pull last N messages from history API
  3. Create Chatwoot contact + conversation + mapping (reuse existing resolveConversation logic)
  4. Post messages with source_id and dedup markers
  5. Config flags: syncOnEnable (default true), syncHistoryLimit (default 50), syncUnreadOnly (default false)

B. Phone / linked-device outgoing sync

  1. Register message:sent hook in chatwoot-adapter
  2. Relay fromMe messages to Chatwoot as outgoing (or incoming from contact perspective — pick one convention and document it; recommend outgoing since it's the account owner's message)
  3. Include source_id: msg.id for threading (chatwoot-adapter: WhatsApp reply/quote context not forwarded to Chatwoot (shows only reply text) #606)
  4. Coordinate with OpenWA core to persist phone-composed sends to messages table (session.service onMessageCreate path) so history backfill and dashboard stay consistent

C. Observability

  • Log skipped vs relayed counts on enable/sync
  • Surface sync errors in plugin status (not only inbound relay failed in logs)

Impact

Without these fixes, Chatwoot is not a faithful inbox mirror of WhatsApp:

  • Agents onboarding see an empty helpdesk
  • Agents using their phone alongside Chatwoot see broken threads
  • Custom sync scripts become mandatory (fragile, text-only, no media)

This is a blocker for production helpdesk use.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions