# How to Create and Manage WhatsApp Channels or Newsletters Using OpenWA

> Learn how to create and manage WhatsApp Channels or newsletters with OpenWA. This guide explains how to use the OpenWA REST API to interact with WhatsApp Channels, simplifying your communication strategy.

- Repository: [Yudhi Armyndharis/OpenWA](https://github.com/rmyndharis/OpenWA)
- Tags: how-to-guide
- Published: 2026-05-21

---

**OpenWA exposes WhatsApp Channels (newsletters) through a REST API and engine layer that maps HTTP requests to [`whatsapp-web.js`](https://github.com/rmyndharis/OpenWA/blob/main/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`](https://github.com/rmyndharis/OpenWA/blob/main/whatsapp-web.js), and the controller exposes HTTP endpoints.

- **Engine Interface**: Declares channel operations in [`src/engine/interfaces/whatsapp-engine.interface.ts`](https://github.com/rmyndharis/OpenWA/blob/main/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`](https://github.com/rmyndharis/OpenWA/blob/main/src/engine/adapters/whatsapp-web-js.adapter.ts), this file contains the concrete implementations of `getSubscribedChannels`, `subscribeToChannel`, and related methods (lines 96–215).
- **REST Controller**: [`src/modules/channel/channel.controller.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/modules/channel/channel.controller.ts) maps HTTP verbs to engine calls under the base route `/sessions/:sessionId/channels`.

The `ChannelModule` (located in [`src/modules/channel/channel.module.ts`](https://github.com/rmyndharis/OpenWA/blob/main/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`](https://github.com/rmyndharis/OpenWA/blob/main/src/engine/interfaces/whatsapp-engine.interface.ts), the contract includes:

```typescript
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`](https://github.com/rmyndharis/OpenWA/blob/main/src/engine/adapters/whatsapp-web-js.adapter.ts), the adapter implements `getSubscribedChannels` by calling the underlying library and normalizing the response:

```typescript
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`](https://github.com/rmyndharis/OpenWA/blob/main/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 via `getSubscribedChannels()`
- **POST** `/sessions/:sessionId/channels/subscribe` – Accepts an `inviteCode` in the body and calls `subscribeToChannel()`
- **GET** `/sessions/:sessionId/channels/:channelId` – Fetches specific channel details using `getChannelById()`
- **GET** `/sessions/:sessionId/channels/:channelId/messages` – Retrieves messages with optional `limit` query parameter via `getChannelMessages()`
- **DELETE** `/sessions/:sessionId/channels/:channelId` – Unsubscribes from a channel using `unsubscribeFromChannel()`

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.

```bash

# 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:

```typescript
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:

```typescript
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 `IWhatsAppEngine` interface and `WhatsAppWebJsAdapter` implementation.
- **File locations**: Interface definitions reside in [`src/engine/interfaces/whatsapp-engine.interface.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/engine/interfaces/whatsapp-engine.interface.ts) (lines 75–81), while the concrete logic is in [`src/engine/adapters/whatsapp-web-js.adapter.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/engine/adapters/whatsapp-web-js.adapter.ts) (lines 96–215).
- **REST endpoints** are exposed under `/sessions/:sessionId/channels` via `ChannelController` in [`src/modules/channel/channel.controller.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/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 `READY` state, enforced by the `ensureReady()` 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`](https://github.com/rmyndharis/OpenWA/blob/main/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`](https://github.com/rmyndharis/OpenWA/blob/main/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.