How to Send and Handle Message Reactions in OpenWA: REST API and SDK Guide

OpenWA enables sending and retrieving WhatsApp message reactions through the POST /sessions/:sessionId/messages/react endpoint and GET .../reactions endpoint, implemented via the whatsapp-web.js adapter layer.

OpenWA is an open-source WhatsApp Web automation framework that exposes message reactions as part of its Extended Messaging API. The system provides type-safe TypeScript interfaces and a modular engine architecture, allowing developers to programmatically add emoji reactions to messages and retrieve reaction metadata using either direct HTTP requests or the official JavaScript SDK.

API Architecture for Message Reactions

The reaction functionality follows a layered architecture that abstracts the underlying WhatsApp library behind a stable REST interface.

Controller and Service Layer

The API Layer exposes HTTP endpoints through src/modules/message/message.controller.ts. The controller handles POST requests for adding or removing reactions and GET requests for retrieving reaction lists. Incoming requests are validated and forwarded to MessageService.reactToMessage() or MessageService.getMessageReactions() defined in src/modules/message/message.service.ts.

The service layer manages session validation and acts as a proxy to the engine, ensuring business logic remains separate from transport concerns.

Engine Interface Contract

All engine implementations must satisfy the IWhatsAppEngine interface declared in src/engine/interfaces/whatsapp-engine.interface.ts. This contract defines the method signatures:

  • reactToMessage(chatId: string, messageId: string, emoji: string): Promise<void>
  • getMessageReactions(chatId: string, messageId: string): Promise<MessageReaction[]>

This abstraction allows OpenWA to support multiple WhatsApp backend implementations while maintaining a consistent API surface.

WhatsApp-Web-JS Adapter Implementation

The concrete implementation resides in src/engine/adapters/whatsapp-web-js.adapter.ts. The WhatsAppWebJsAdapter class:

  1. Locates the target chat and message by fetching the last 100 messages from the chat history
  2. Invokes Message.react(emoji) to add reactions or Message.getReactions() to retrieve them
  3. Transforms library-specific objects into the generic MessageReaction interface defined in src/engine/types/whatsapp-web-js.types.ts

Sending Message Reactions

REST Endpoint Specification

To add or remove a reaction, send a POST request to:


POST /sessions/:sessionId/messages/react

Request body structure:

{
  "chatId": "12345@c.us",
  "messageId": "true_1234567890@c.us",
  "emoji": "👍"
}

Key parameters:

  • chatId: The WhatsApp ID of the conversation (e.g., 12345@c.us for individuals or 123456789@g.us for groups)
  • messageId: The serialized message ID (prefixed with true_ for standard messages)
  • emoji: The Unicode emoji character to react with; pass an empty string "" to remove the reaction

SDK Implementation

Using the official JavaScript SDK:

import { OpenWASdk } from '@openwa/sdk';

const sdk = new OpenWASdk({ baseUrl: 'http://localhost:3000' });

await sdk.sessions('sess-1')
         .messages
         .react({
           chatId: '12345@c.us',
           messageId: 'true_1678901234@c.us',
           emoji: '👍'
         });

Removing Reactions

To remove a reaction, pass an empty string as the emoji value:

await sdk.sessions('sess-1')
         .messages
         .react({
           chatId: '12345@c.us', 
           messageId: 'true_1678901234@c.us',
           emoji: ''
         });

The underlying whatsapp-web.js library interprets the empty payload as a removal command.

Retrieving Message Reactions

GET Endpoint Structure

Fetch all reactions for a specific message using:


GET /sessions/:sessionId/messages/:chatId/:messageId/reactions

The controller method MessageController.getReactions() invokes the adapter's getMessageReactions() method, which:

  • Validates the session readiness via this.ensureReady()
  • Retrieves the chat instance and locates the message within the last 100 fetched messages
  • Calls Message.getReactions() and maps the results to the MessageReaction type

Response Format

The endpoint returns an array of MessageReaction objects:

[
  {
    "emoji": "👍",
    "senders": [
      { "senderId": "12345@c.us", "emoji": "👍", "timestamp": 1706400000 }
    ]
  },
  {
    "emoji": "❤️",
    "senders": [
      { "senderId": "67890@c.us", "emoji": "❤️", "timestamp": 1706400100 }
    ]
  }
]

Each reaction object contains the emoji character and a list of senders who applied that reaction, including their WhatsApp ID, the specific emoji used, and the UNIX timestamp of the reaction.

Implementation Examples

Direct HTTP with cURL

Add a reaction:

curl -X POST "http://localhost:3000/api/sessions/sess-1/messages/react" \
     -H "Content-Type: application/json" \
     -d '{"chatId":"12345@c.us","messageId":"true_1678901234@c.us","emoji":"👍"}'

Retrieve reactions:

curl "http://localhost:3000/api/sessions/sess-1/messages/12345@c.us/true_1678901234@c.us/reactions"

Adapter Internal Logic

The WhatsAppWebJsAdapter.reactToMessage() method in src/engine/adapters/whatsapp-web-js.adapter.ts (lines 95-104) implements the low-level interaction:

async reactToMessage(chatId: string, messageId: string, emoji: string): Promise<void> {
  this.ensureReady();
  const chat = await this.client!.getChatById(chatId);
  const messages = await chat.fetchMessages({ limit: 100 });
  const message = messages.find(m => m.id._serialized === messageId);
  if (!message) throw new Error(`Message ${messageId} not found in chat ${chatId}`);
  await (message as MessageWithReactions).react(emoji);
  this.logger.log(`Reacted to message ${messageId} with ${emoji || '(removed)'}`);
}

For retrieval, getMessageReactions() (lines 107-136) handles the transformation:

async getMessageReactions(chatId: string, messageId: string): Promise<MessageReaction[]> {
  this.ensureReady();
  const chat = await this.client!.getChatById(chatId);
  const messages = await chat.fetchMessages({ limit: 100 });
  const message = messages.find(m => m.id._serialized === messageId);
  if (!message) throw new Error(`Message ${messageId} not found in chat ${chatId}`);
  const msgWithReactions = message as MessageWithReactions;
  if (!msgWithReactions.hasReaction) return [];

  const reactions = await msgWithReactions.getReactions();
  if (!reactions) return [];

  return reactions.map(r => ({
    emoji: String(r.id),
    senders: (r.senders ?? []).map(s => ({
      senderId: String(s.senderId),
      emoji: String(s.reaction),
      timestamp: Number(s.timestamp),
    })),
  }));
}

Summary

  • Reaction endpoints: Use POST /sessions/:sessionId/messages/react to add or remove reactions, and GET /sessions/:sessionId/messages/:chatId/:messageId/reactions to retrieve them
  • Architecture flow: Requests traverse MessageController → MessageService → IWhatsAppEngine interface → WhatsAppWebJsAdapter → whatsapp-web.js library
  • Data model: Reactions are returned as MessageReaction arrays containing emoji characters and sender metadata with timestamps
  • Removal mechanism: Pass an empty string "" as the emoji parameter to remove an existing reaction
  • Scope limitation: The adapter searches within the last 100 messages of a chat to locate the target message by its serialized ID

Frequently Asked Questions

How do I remove a reaction in OpenWA?

Pass an empty string "" as the emoji value in the POST /sessions/:sessionId/messages/react request. The whatsapp-web.js library interprets empty payloads as removal commands, and the adapter logs this action accordingly.

What is the maximum number of messages searched when adding a reaction?

The WhatsAppWebJsAdapter fetches the last 100 messages from the chat history using chat.fetchMessages({ limit: 100 }) to locate the target message by its serialized ID. If the message is older than the 100 most recent messages, the operation will fail with a "Message not found" error.

Can I use custom emojis or stickers for reactions?

No, OpenWA only supports standard Unicode emoji characters through the WhatsApp Web protocol. The emoji parameter accepts single Unicode emoji characters (e.g., "👍", "❤️", "😂") but does not support custom images or stickers for the reaction feature.

What data structure defines a reaction in the OpenWA codebase?

Reactions follow the MessageReaction interface defined in src/engine/types/whatsapp-web-js.types.ts (lines 37-45). Each reaction contains an emoji string and a senders array, where each sender object includes senderId (WhatsApp JID), emoji (the reaction character), and timestamp (UNIX epoch in milliseconds).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →