How to Create and Manage WhatsApp Channels or Newsletters Using OpenWA
OpenWA exposes WhatsApp Channels (newsletters) through a REST API and engine layer that maps HTTP requests to whatsapp-web.js BusinessClient methods for subscribing, unsubscribing, and retrieving channel content.
OpenWA (rmyndharis/OpenWA) is a Node.js framework that wraps WhatsApp Web functionality in a NestJS-based architecture. While you cannot create new WhatsApp Channels through the API—since WhatsApp restricts channel creation to the mobile app—you can fully manage subscriptions and consume content programmatically. This guide covers the engine interface, REST endpoints, and implementation details for integrating WhatsApp Channels into your application.
Architecture Overview
OpenWA organizes channel functionality into three distinct layers. The engine interface defines the contract, the adapter implements the logic using whatsapp-web.js, and the controller exposes HTTP endpoints.
- Engine Interface: Declares channel operations in
src/engine/interfaces/whatsapp-engine.interface.ts(lines 75–81). This ensures any underlying WhatsApp library adheres to the same API contract. - Engine Adapter: Located in
src/engine/adapters/whatsapp-web-js.adapter.ts, this file contains the concrete implementations ofgetSubscribedChannels,subscribeToChannel, and related methods (lines 96–215). - REST Controller:
src/modules/channel/channel.controller.tsmaps HTTP verbs to engine calls under the base route/sessions/:sessionId/channels.
The ChannelModule (located in src/modules/channel/channel.module.ts) registers the controller with the global SessionModule, ensuring channel endpoints are available once a session is initialized.
Engine Interface and Implementation
The IWhatsAppEngine interface declares five core methods for channel management. These are implemented in the WhatsAppWebJsAdapter class, which casts the generic client to BusinessClient to access WhatsApp Business API features.
Interface Definition
According to src/engine/interfaces/whatsapp-engine.interface.ts, the contract includes:
export interface IWhatsAppEngine {
getSubscribedChannels(): Promise<Channel[]>;
getChannelById(channelId: string): Promise<Channel | null>;
subscribeToChannel(inviteCode: string): Promise<Channel>;
unsubscribeFromChannel(channelId: string): Promise<void>;
getChannelMessages(channelId: string, limit?: number): Promise<ChannelMessage[]>;
}
Adapter Implementation
In src/engine/adapters/whatsapp-web-js.adapter.ts, the adapter implements getSubscribedChannels by calling the underlying library and normalizing the response:
async getSubscribedChannels(): Promise<Channel[]> {
this.ensureReady();
const channels = await (this.client as unknown as BusinessClient).getChannels();
if (!channels) return [];
return channels.map((ch: WwjsChannelData) => ({
id: String(typeof ch.id === 'object' ? ch.id._serialized : ch.id),
name: String(ch.name || ''),
description: ch.description ? String(ch.description) : undefined,
inviteCode: ch.inviteCode ? String(ch.inviteCode) : undefined,
subscriberCount: ch.subscriberCount ? Number(ch.subscriberCount) : undefined,
verified: ch.verified ? Boolean(ch.verified) : undefined,
}));
}
Each method begins with ensureReady() (lines 187–191 in the adapter), which validates that the WhatsApp client has reached the READY state before executing Business API calls.
REST API Endpoints
The ChannelController (defined in src/modules/channel/channel.controller.ts) exposes five endpoints that authenticate via sessionId parameter and proxy requests to the engine layer:
- GET
/sessions/:sessionId/channels– Returns all subscribed channels viagetSubscribedChannels() - POST
/sessions/:sessionId/channels/subscribe– Accepts aninviteCodein the body and callssubscribeToChannel() - GET
/sessions/:sessionId/channels/:channelId– Fetches specific channel details usinggetChannelById() - GET
/sessions/:sessionId/channels/:channelId/messages– Retrieves messages with optionallimitquery parameter viagetChannelMessages() - DELETE
/sessions/:sessionId/channels/:channelId– Unsubscribes from a channel usingunsubscribeFromChannel()
The controller retrieves the engine instance through SessionService.getEngine(sessionId), ensuring that requests are routed to the correct WhatsApp Web client.
Code Examples
Listing and Subscribing via HTTP API
Use standard HTTP requests to manage channel subscriptions. All endpoints return JSON objects matching the Channel or ChannelMessage interfaces defined in the engine layer.
# List all subscribed channels for a specific session
curl -X GET "https://your-host/api/sessions/abc123/channels" \
-H "Authorization: Bearer <your-token>"
# Subscribe to a channel using an invite code
curl -X POST "https://your-host/api/sessions/abc123/channels/subscribe" \
-H "Content-Type: application/json" \
-d '{"inviteCode":"ABC123xyz"}'
# Retrieve the latest 20 messages from a channel
curl -X GET "https://your-host/api/sessions/abc123/channels/12345/messages?limit=20"
# Unsubscribe from a channel
curl -X DELETE "https://your-host/api/sessions/abc123/channels/12345"
Managing Channels with the JavaScript SDK
When using the OpenWA SDK client, channel operations mirror the REST API but handle session management internally:
import { OpenWA } from '@open-wa/sdk';
const client = new OpenWA({ sessionId: 'abc123' });
await client.start();
// Fetch all subscribed channels
const channels = await client.getSubscribedChannels();
console.log(`Found ${channels.length} channels`);
// Subscribe using an invite link code
const newChannel = await client.subscribeToChannel('XYZ789abc');
console.log(`Subscribed to: ${newChannel.name}`);
// Get recent messages (defaults to 50 if limit omitted)
const messages = await client.getChannelMessages(newChannel.id, 10);
messages.forEach(m => console.log(`[${m.timestamp}] ${m.body}`));
// Unsubscribe when finished
await client.unsubscribeFromChannel(newChannel.id);
The SDK forwards these calls to the same engine methods used by the REST controller, ensuring consistent behavior across interfaces.
Integrating Channel Logic into NestJS Services
For custom business logic, inject the SessionService directly and interact with the engine layer:
import { Injectable } from '@nestjs/common';
import { SessionService } from '../session/session.service';
@Injectable()
export class NewsletterService {
constructor(private readonly sessionService: SessionService) {}
async getAllChannels(sessionId: string) {
const engine = this.sessionService.getEngine(sessionId);
return engine.getSubscribedChannels();
}
async joinChannel(sessionId: string, inviteCode: string) {
const engine = this.sessionService.getEngine(sessionId);
return engine.subscribeToChannel(inviteCode);
}
async leaveChannel(sessionId: string, channelId: string) {
const engine = this.sessionService.getEngine(sessionId);
await engine.unsubscribeFromChannel(channelId);
}
}
Summary
- OpenWA maps WhatsApp Channels to a structured API through the
IWhatsAppEngineinterface andWhatsAppWebJsAdapterimplementation. - File locations: Interface definitions reside in
src/engine/interfaces/whatsapp-engine.interface.ts(lines 75–81), while the concrete logic is insrc/engine/adapters/whatsapp-web-js.adapter.ts(lines 96–215). - REST endpoints are exposed under
/sessions/:sessionId/channelsviaChannelControllerinsrc/modules/channel/channel.controller.ts. - Operations supported: Listing subscribed channels, subscribing via invite code, fetching channel metadata, retrieving message history, and unsubscribing.
- Requirement: The WhatsApp client must be in
READYstate, enforced by theensureReady()method in the adapter.
Frequently Asked Questions
How do I subscribe to a WhatsApp Channel using OpenWA?
Send a POST request to /sessions/:sessionId/channels/subscribe with a JSON body containing the inviteCode, or call engine.subscribeToChannel(inviteCode) directly. The adapter validates the client state, forwards the request to whatsapp-web.js, and returns a normalized Channel object containing the channel ID, name, and subscriber count.
Can I create a new WhatsApp Channel using OpenWA?
No, OpenWA only supports subscribing to existing channels and retrieving their content. WhatsApp restricts channel creation to the mobile application interface, so the subscribeToChannel method requires an existing inviteCode generated from the WhatsApp mobile client.
What data is returned when retrieving channel messages?
The getChannelMessages method returns an array of ChannelMessage objects, each containing properties such as id, body, timestamp, and author. This data is extracted from the BusinessClient response in whatsapp-web-js.adapter.ts (lines 197–215) and formatted to provide a stable API contract independent of the underlying library's raw output.
How does OpenWA handle multiple WhatsApp sessions with channels?
Each session maintains its own engine instance via the SessionService. When you call endpoints under /sessions/:sessionId/channels, the controller looks up the specific engine for that session ID. This isolation ensures that channel subscriptions and message history remain separate across different WhatsApp accounts managed by the same OpenWA instance.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →