# How DeskcommCRM Integrates with WhatsApp Using WAHA: A Technical Deep Dive

> Discover how DeskcommCRM integrates with WhatsApp via WAHA using a three-layer TypeScript architecture. Learn about session management, channel adapters, and graceful degradation for seamless communication.

- Repository: [Rafael Melgaço/DeskcommCRM](https://github.com/melgarafael/DeskcommCRM)
- Tags: deep-dive
- Published: 2026-09-12

---

**DeskcommCRM integrates with WhatsApp through the WAHA (WhatsApp-as-a-Home-Assistant) Docker container using a three-layer TypeScript architecture that abstracts HTTP session management, implements a generic channel adapter interface, and provides graceful degradation when the container is unavailable.**

DeskcommCRM enables businesses to manage WhatsApp conversations at scale by connecting to the open-source WAHA API. This integration employs a modular TypeScript design that transforms WhatsApp's complex session lifecycle into clean REST operations while maintaining security and performance. Understanding how DeskcommCRM connects to WhatsApp via WAHA reveals enterprise-grade patterns for messaging infrastructure that prioritize reliability and error resilience.

## Architecture Overview

The integration follows a strict separation of concerns across three logical layers, each handling distinct responsibilities within the messaging pipeline.

| Layer | Responsibility | Implementation |
|-------|----------------|----------------|
| **Client** | Low-level REST wrapper handling HTTP timeouts, token authentication, and error translation | [[`lib/waha/client.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/waha/client.ts)](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/waha/client.ts) |
| **Adapter** | Implements the generic `ChannelAdapter` interface used by the CRM's messaging engine | [[`lib/channels/adapters/waha.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/channels/adapters/waha.ts)](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/channels/adapters/waha.ts) |
| **Server** | Utilities parsing WAHA version/engine capabilities from the version endpoint | [[`lib/channels/waha-server.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/channels/waha-server.ts)](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/channels/waha-server.ts) |

This layered approach ensures that the CRM core remains agnostic of WhatsApp-specific implementation details while the adapter handles protocol translation.

## Configuration and Environment Setup

WAHA integration is **optional and degrades gracefully**. The system checks for required environment variables before initializing the client stack.

```typescript
// lib/waha/client.ts → getWahaClient()
export function getWahaClient(): WahaClient | null {
  const url = process.env.WAHA_API_BASE_URL;
  const key = process.env.WAHA_API_KEY;
  if (!url || !key || key === "dev_plaintext_change_me") return null;
  return new WahaClient(url, key);
}

```

- **`WAHA_API_BASE_URL`**: The endpoint of the WAHA container (e.g., `http://localhost:3030`).
- **`WAHA_API_KEY`**: The plain-text API key passed in the `X-Api-Key` header.

When these variables are absent, `getWahaClient()` returns `null`. Downstream code checks `isConfigured()` on the adapter, rendering the integration a **no-op** rather than throwing runtime errors. This allows the UI to display "container not running" banners instead of crashing.

## Session Lifecycle Management

WAHA sessions correspond directly to individual WhatsApp phone numbers. The `WahaClient` class in [`lib/waha/client.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/waha/client.ts) wraps the session REST endpoints with strict timeout handling.

Key session operations include:

- **`createSession(name)`**: POST to `/api/sessions` with `config.ignore` filters to discard group and broadcast events.
- **`startSession(name)`**: Ensures session existence then POST to `/api/sessions/:name/start`.
- **`getSessionQr(name)`**: GET `/api/sessions/:name` for QR code retrieval during authentication.
- **`stopSession / logoutSession / deleteSession`**: Corresponding POST/DELETE endpoints for session termination.

All network calls route through `fetchComTeto`, which enforces configurable timeouts:

- **`TETO_PADRAO_MS = 15000`**: Standard 15-second timeout for API calls.
- **`TETO_DE_MIDIA_MS = 30000`**: Extended 30-second timeout for media operations.

Expired timeouts throw deterministic errors like `waha_timeout`, enabling precise debugging and retry logic.

## Messaging Implementation via the Adapter Pattern

The **`wahaAdapter`** in [`lib/channels/adapters/waha.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/channels/adapters/waha.ts) implements the `ChannelAdapter` interface required by DeskcommCRM's messaging pipeline. This abstraction allows the CRM to treat WhatsApp identically to other communication channels.

```typescript
// lib/channels/adapters/waha.ts → wahaAdapter.send()
async send(envelope: OutboundEnvelope): Promise<{ externalId: string | null }> {
  const client = getWahaClient();
  if (!client) return { externalId: null };          // Graceful no-op

  const to = await resolveCanonicalCusChatId(client, envelope.sessionRef, envelope.to);
  let res: unknown;

  if (envelope.kind === "contact" && envelope.contact) {
    res = await client.sendContactVcard(...);     // /api/sendContactVcard
  } else if (envelope.media) {
    res = await client.sendMedia(...);              // Media endpoints
  } else {
    res = await client.sendMessage(...);            // /api/sendText
  }
  return { externalId: parseWahaMessageId(res) };
}

```

**Message type handling:**

- **Text messages**: `client.sendMessage(session, chatId, text, replyTo?)` targets `/api/sendText`.
- **Media files**: `client.sendMedia(session, chatId, plan)` where `plan` is constructed by [`lib/waha/media-send.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/waha/media-send.ts) to handle images, video, and documents.
- **Contact vCards**: `client.sendContactVcard()` exports phone contacts via `/api/sendContactVcard`.

The adapter also provides auxiliary capabilities:

- **`fetchProfilePictureUrl`**: Retrieves signed CDN URLs for contact avatars.
- **`resolvePhoneForIdentity`**: Translates opaque WhatsApp IDs (`lid@lid`) back to phone numbers.
- **`signalTyping`**: Triggers typing indicators via `client.setPresence`.

## Health Monitoring and Error Handling

Before each outbound operation, the system performs health checks to determine session status and credential validity.

```typescript
// lib/channels/adapters/waha.ts → checkHealth()
async checkHealth({ sessionRef }) {
  const client = getWahaClient();
  if (!client) return { reachable: false, status: null, detail: "transporte_nao_configurado" };
  
  try {
    const r = await client.getSessionQr(sessionRef);
    return { reachable: true, status: r.status ?? null, detail: null };
  } catch (err) {
    const http = statusHttpDoErroWaha(err.message);
    if (http === 404) return { reachable: true, status: "STOPPED", detail: null };
    if (http === 401 || http === 403) return { reachable: false, status: null, detail: DETALHE_CREDENCIAL_RECUSADA };
    return { reachable: false, status: null, detail: err.message.slice(0,200) };
  }
}

```

**HTTP status mapping:**

- **404**: Session stopped (WAHA reports "Session not found").
- **401/403**: Authentication failure flagged as `DETALHE_CREDENCIAL_RECUSADA`.
- **Timeouts**: Surface as `waha_timeout` with operation-specific prefixes like `waha_sendText_504`.

The `wahaFriendlyError` utility at the end of [`client.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/client.ts) beautifies these deterministic error codes for end-user display.

## Media Handling and Security

Incoming media requires special handling to prevent SSRF attacks and manage signed URL expiration.

```typescript
// lib/channels/adapters/waha.ts → fetchInboundMedia()
async fetchInboundMedia({ url, hintMime }) {
  return fetchWahaMedia(url, hintMime ?? null);   // lib/messaging/media/waha-source.ts
}

```

The `fetchWahaMedia` function in [`lib/messaging/media/waha-source.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/messaging/media/waha-source.ts) rewrites host and port information to the configured WAHA base URL, ensuring that requests route exclusively through the controlled container environment. This prevents Server-Side Request Forgery while handling WhatsApp's temporary signed CDN URLs.

## Performance Optimization with Event Filtering

To reduce network, CPU, and storage overhead, the client configures WAHA to ignore irrelevant conversation types. The `CONVERSAS_IGNORADAS` constant injected into every session's `config.ignore` parameter filters out:

- Group chats
- Broadcast lists  
- Newsletter channels

This filter is applied during session creation in [`lib/waha/client.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/waha/client.ts), significantly decreasing webhook noise for CRM instances handling high message volumes.

## Summary

DeskcommCRM's WhatsApp integration via WAHA demonstrates several architectural best practices:

- **Graceful degradation** through environment-based client initialization that returns `null` rather than throwing.
- **Layered abstraction** separating HTTP concerns (`WahaClient`), protocol translation (`wahaAdapter`), and business logic.
- **Deterministic error handling** with specific timeout constants (`TETO_PADRAO_MS`, `TETO_DE_MIDIA_MS`) and parsable error codes.
- **Security-first media handling** via URL rewriting in `fetchWahaMedia` to prevent SSRF vulnerabilities.
- **Performance optimization** through the `CONVERSAS_IGNORADAS` filter that eliminates processing of group and broadcast events.

## Frequently Asked Questions

### How do I configure DeskcommCRM to connect to a WAHA container?

Set the environment variables `WAHA_API_BASE_URL` (pointing to your Docker container, e.g., `http://localhost:3030`) and `WAHA_API_KEY` (matching the container's `X-Api-Key` header). If either is missing or set to the placeholder `dev_plaintext_change_me`, the integration gracefully disables itself, allowing the application to run without WhatsApp capabilities.

### What happens when a WhatsApp session times out or the container is unreachable?

The `fetchComTeto` wrapper enforces strict timeouts—15 seconds for standard operations and 30 seconds for media. When exceeded, it throws specific error codes like `waha_timeout` that the adapter catches and translates into user-friendly messages via `wahaFriendlyError`. The `checkHealth()` method also proactively detects connectivity issues before attempting sends.

### How does DeskcommCRM handle different message types (text, images, contacts)?

The `wahaAdapter.send()` method inspects the `envelope.kind` and `envelope.media` properties to route requests appropriately. Text uses `/api/sendText`, media routes through `/api/sendImage` or related endpoints via [`lib/waha/media-send.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/waha/media-send.ts), and contacts utilize `/api/sendContactVcard`. Each path returns a parsed `externalId` for message tracking.

### Is the WhatsApp integration secure against SSRF attacks?

Yes. When fetching inbound media through `fetchInboundMedia()`, the `fetchWahaMedia` function in [`lib/messaging/media/waha-source.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/messaging/media/waha-source.ts) rewrites the URL host and port to match the configured WAHA base URL. This ensures that the CRM only requests resources from the trusted WAHA container, preventing Server-Side Request Forgery attacks via malicious media URLs.