Weplex Engineering - The bot architeture and processing Whatsapp async messages

Introduction

Today we implemented the core Messaging Processing infrastructure for our Node.js chatbot. Leveraging the WhatsApp Webhook model, we came up with a receiver for incoming callbacks, deduplicates messages, marks them as read, and echoes a response—triggering WhatsApp’s “double‑check” (✓✓) receipt experience for the user.

Collaborators

  • Roger Soares
  • Marcio Galli

Complication

WhatsApp can batch multiple messages (and retries) into a single webhook invocation. Without proper handling:

  • Duplicates: Retries or overlapping deliveries can cause the same message to be processed more than once, leading to repeated replies.
  • Concurrency: Parallel processing of messages may race against shared state (e.g., marking messages read or persisting conversation context).
  • User experience: Extra or missing acknowledgments confuse end users and break the expected flow of check‑marks (from single to double ticks).

To address these challenges, we must support ordered processing pipeline that safely handles multiple incoming messages per webhook, ensuring each is processed exactly once and in a predictable sequence.

WhatsApp Webhook Flow

  1. Authentication (GET)

    • WhatsApp sends hub.mode, hub.verify_token and hub.challenge.
    • We verify the token and return the challenge on success.
  2. Incoming Messages (POST)

    • WhatsApp posts JSON to our /whatsapp-webhook endpoint.
    • We immediately acknowledge with 200 EVENT_RECEIVED to prevent retries.

Message Processing Infrastructure

  • Express handles JSON parsing and routing.
  • Deduplication: We record each messageId in MongoDB’s ProcessedMessages.
  • Read Receipts: Call markMessageAsRead() so user sees ✓✓.
  • Handler: A lightweight defaultHandler echoes “OK,” completing the acknowledgment cycle.
for each entry → for each change → for each message:
  if not in ProcessedMessages:
    insert message.id
    markMessageAsRead(...)
    defaultHandler(...)

Deduplication with ProcessedMessages

  • Check: findOne({ messageId }) prevents re‑processing duplicates.
  • Insert: New messageId + timestamp marks it as handled.
  • Outcome: Exactly‑once processing even under retries or batch deliveries.

Read Receipts

  • After dedupe, we invoke:

    await markMessageAsRead(phone_number_id, message.id)
  • User sees two gray ticks turn blue as soon as our reply is accepted by WhatsApp.

Default Handler & Echo Response

  • Current implementation simply sends back “OK”:

    await sendMessage(pnId, from, 'OK')
  • This minimal echo confirms our pipeline works end‑to‑end before adding real business logic.

Example

© 2019–2025 Weplex Comunicação. Atendemos presencialmente em Ribeirão Preto, na Maurílio Biagi 800, Sala 1104 (SP) ou remotamente, profissionais liberais de todo Brasil e do mundo.

Meplex Comunicações, Weplex Comunicações, Weplex Communications e xBio são marcas registradas de Weplex Comunicações. Todo o conteúdo deste site está protegido por direitos autorais © 2019–2025 Weplex Comunicações. Todos os direitos reservados.

Feito com ❤ por Weplex