How DeskcommCRM Integrates with WhatsApp Using WAHA: A Technical Deep Dive
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) |
| 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) |
| 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) |
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.
// 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 theX-Api-Keyheader.
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 wraps the session REST endpoints with strict timeout handling.
Key session operations include:
createSession(name): POST to/api/sessionswithconfig.ignorefilters to discard group and broadcast events.startSession(name): Ensures session existence then POST to/api/sessions/:name/start.getSessionQr(name): GET/api/sessions/:namefor 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 implements the ChannelAdapter interface required by DeskcommCRM's messaging pipeline. This abstraction allows the CRM to treat WhatsApp identically to other communication channels.
// 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)whereplanis constructed bylib/waha/media-send.tsto 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 viaclient.setPresence.
Health Monitoring and Error Handling
Before each outbound operation, the system performs health checks to determine session status and credential validity.
// 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_timeoutwith operation-specific prefixes likewaha_sendText_504.
The wahaFriendlyError utility at the end of 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.
// 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 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, 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
nullrather 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
fetchWahaMediato prevent SSRF vulnerabilities. - Performance optimization through the
CONVERSAS_IGNORADASfilter 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, 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 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.
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 →