WAHA 2026.7.2 NOWEB Engine Integration: Webhook, QR‑Code Onboarding, and Session Persistence in DeskcommCRM
The WAHA 2026.7.2 NOWEB engine integrates with DeskcommCRM through a public webhook endpoint that receives QR‑code scan events, maps them to persisted database sessions with status tracking, and enables real‑time UI polling to maintain connections across browser reloads.
DeskcommCRM implements a complete lifecycle management system for the WAHA 2026.7.2 NOWEB engine that couples Docker‑hosted WAHA containers with a Next.js backend and Postgres persistence. The integration relies on capability detection, Zod‑validated webhook payloads, and a state machine that transitions sessions from STARTING through SCAN_QR_CODE to WORKING.
Engine Discovery and Capability Detection
Before establishing any connection, the platform queries the WAHA server to determine supported features. The describeWahaServer function in lib/channels/waha-server.ts accepts version, engine, and tier parameters to return capability flags.
For version 2026.7.2 with engine NOWEB, this function returns multipleSessions: "supported" at line 26. This flag is critical because it instructs the orchestration layer that the engine can manage multiple concurrent QR‑code sessions simultaneously, enabling multi‑tenant deployments where different organizations maintain separate WhatsApp connections.
// lib/channels/waha-server.ts
import { describeWahaServer } from "@/lib/channels/waha-server";
const caps = describeWahaServer({
version: "2026.7.2",
engine: "NOWEB",
tier: "standard"
});
// caps.multipleSessions === "supported"
Webhook Configuration and Endpoint Setup
WAHA communicates with DeskcommCRM via HTTP POST requests to a dedicated webhook endpoint. According to the runbook in docs/runbooks/waha-hostgator.md, the Docker container sets the environment variable WHATSAPP_HOOK_URL to https://<your-domain>/api/webhooks/waha (lines 41‑43).
The WAHA engine emits JSON events whenever the session state changes—particularly when generating a new QR code for authentication. Nginx reverse‑proxies these requests to the Next.js application, where the route handler in app/api/v1/webhooks/waha/route.ts processes the payload using generic ok() and fail() wrappers from lib/api/wrappers.ts.
Validating and Mapping Webhook Payloads
Incoming webhook data undergoes strict validation before database insertion. The specification in docs/specs/03-spec-whatsapp-waha.md defines a Zod schema that expects fields including name, status, and engine.
When WAHA sends a SCAN_QR_CODE event, the connectWahaChannel function in lib/channels/connect-waha.ts triggers. This function creates a new row in the channel_sessions table with status: "SCAN_QR_CODE", deriving the session_name from the phone number contained in the QR payload. The implementation handles the state transition from STARTING to SCAN_QR_CODE as defined in the Zod schema at line 10.
// Simplified webhook handler
import { z } from "zod";
import { ok, fail } from "@/lib/api/wrappers";
import { createWahaSession } from "@/lib/supabase/server";
const webhookSchema = z.object({
name: z.string(),
status: z.enum(["STARTING", "SCAN_QR_CODE", "WORKING"]),
engine: z.string(),
});
export async function POST(req: Request) {
const data = await req.json();
const parsed = webhookSchema.safeParse(data);
if (!parsed.success) return fail(400, "invalid_payload");
const { name, status } = parsed.data;
await createWahaSession({ waha_session_name: name, status });
return ok({ received: true });
}
Database Persistence and Multi‑Tenancy
Session state survives application restarts through Postgres persistence. The lib/supabase/server.ts service‑role client exposes an RPC named fn_create_waha_session that writes session records linking each WAHA session to a specific tenant via organization_id.
The database stores status values as a Postgres enum with states: STARTING, SCAN_QR_CODE, WORKING, STOPPED, and FAILED. Because the NOWEB engine is stateless on the client side, storing the session_id in the channel_sessions row allows the platform to resume operations after browser refreshes or server redeployments.
Frontend Integration and Real‑Time Polling
The React frontend monitors database changes through the useChannelSessions hook defined in hooks/channels/useChannelSessions.ts. This hook watches for rows where status is STARTING or SCAN_QR_CODE.
When the hook detects SCAN_QR_CODE, it renders a QR code UI component and initiates a 3‑second polling interval to the WAHA /api/sessions/:name endpoint. This polling continues until the status advances to WORKING, at which point the session is considered authenticated and ready for message transmission.
// hooks/channels/useChannelSessions.ts
import { useChannelSessions } from "@/hooks/channels/useChannelSessions";
export function QRCodeOnboarding({ orgId }: { orgId: string }) {
const { sessions, state } = useChannelSessions(orgId);
if (state === "connecting") return <Spinner />;
if (sessions.some(s => s.status === "SCAN_QR_CODE")) {
return <QRCodeDisplay sessionName={sessions[0].waha_session_name!} />;
}
return null;
}
Health Monitoring and Automated Recovery
Prolonged SCAN_QR_CODE states indicate potential connection failures. The health monitoring system in lib/channels/health.ts treats this status as a critical alert (STATUS_QUE_AVISAM).
The watchdog worker in workers/agent-watchdog.ts continuously scans for sessions stuck in the QR code phase beyond expected timeouts. When detected, the worker can trigger a Docker container restart for the specific WAHA instance, forcing a fresh session generation without manual intervention.
Summary
- Capability Detection: The
describeWahaServerfunction inlib/channels/waha-server.tsreportsmultipleSessions: "supported"for WAHA 2026.7.2 NOWEB, enabling concurrent multi‑tenant sessions. - Webhook Integration: The platform exposes
/api/webhooks/wahato receive state change events, configured via theWHATSAPP_HOOK_URLenvironment variable in the Docker deployment. - State Persistence: Postgres stores session states as enums (
STARTING,SCAN_QR_CODE,WORKING), withfn_create_waha_sessionlinking each session to anorganization_idfor tenant isolation. - UI Resilience: The
useChannelSessionshook polls the database every 3 seconds, rendering QR codes during theSCAN_QR_CODEphase and maintaining connection state across page reloads. - Failure Recovery: The
agent-watchdog.tsworker monitors for stuck sessions and automates WAHA container restarts when QR code scanning fails to complete.
Frequently Asked Questions
How does DeskcommCRM handle multiple concurrent WAHA sessions?
The platform leverages the multipleSessions: "supported" flag returned by describeWahaServer in lib/channels/waha-server.ts. Because WAHA 2026.7.2 NOWEB explicitly advertises this capability, the orchestration layer can spawn separate Docker containers and database rows for each tenant’s organization_id, isolating WhatsApp connections while sharing the same webhook endpoint.
What happens to active sessions when the browser refreshes?
Sessions persist because the NOWEB engine stores all state in the Postgres database rather than browser memory. The channel_sessions table retains the session_id and status fields, and the useChannelSessions hook re‑hydrates the UI state on mount by querying the latest database row. This design ensures that a QR‑code scan in progress survives page reloads without requiring re‑authentication.
Which database fields track the QR‑code onboarding progress?
The channel_sessions table uses a Postgres enum for the status column with values including STARTING, SCAN_QR_CODE, and WORKING. When WAHA generates a new QR code via webhook, the connectWahaChannel function in lib/channels/connect-waha.ts updates this column to SCAN_QR_CODE. The session_name field stores the phone number identifier, while foreign keys link the record to the tenant’s organization_id.
How is the webhook payload validated for security?
All incoming requests to /api/webhooks/waha pass through Zod schema validation defined in docs/specs/03-spec-whatsapp-waha.md. The schema requires fields such as name, status, and engine, rejecting malformed payloads with a 400 response via the fail() wrapper in lib/api/wrappers.ts. This prevents database pollution from unauthorized or corrupted webhook calls.
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 →