How DeskcommCRM Secures WAHA Webhooks with HMAC Verification
DeskcommCRM validates every inbound WAHA webhook using SHA-512 HMAC verification in lib/waha/webhook-auth.ts, enforcing cryptographic authentication through the x-webhook-hmac header while supporting configurable strict mode via environment variables.
DeskcommCRM implements robust cryptographic authentication for inbound WAHA webhooks to prevent forged events and ensure message integrity. The open-source CRM uses SHA-512 HMAC verification as implemented in the lib/waha/webhook-auth.ts module, offering both mandatory and optional signature modes depending on deployment requirements.
Core HMAC Verification Flow
The authentication logic centers on the authenticateWahaWebhook function exported from lib/waha/webhook-auth.ts. When a webhook request arrives, the system extracts three critical pieces of data before applying three enforced security rules.
Required Input Data
rawBody– The exact request payload bytes as received from WAHA.signatureHeader– The value from thex-webhook-hmac(case-insensitive) HTTP header.sessionSecret– The decrypted secret for the specific WAHA session, or the global fallbackWAHA_HMAC_SECRETenvironment variable.
The Three Security Rules (Lines 13–27)
The implementation applies these rules in order:
- Bad Signature Rejection – If the header is present but the computed HMAC does not match the payload, the request returns
{ ok: false, reason: "bad_signature" }and is blocked immediately. - Signature Required Mode – When
WAHA_WEBHOOK_REQUIRE_SIGNATUREis set to"true", missing or empty headers trigger rejection with{ ok: false, reason: "signature_required" }. - Optional Verification – If the header is absent and strict mode is disabled, the request proceeds with
signatureVerified: falselogged for audit purposes.
Cryptographic Implementation Details
The verifyHmacSha512 Helper
The actual cryptographic verification delegates to verifyHmacSha512(rawBody, signatureHeader, secret), imported from lib/waha/ingest.ts. This function:
- Recomputes the SHA-512 HMAC of the raw body using the provided secret.
- Compares the result using
crypto.timingSafeEqualto prevent timing attacks. - Enforces a minimum secret length of 16 bytes (
MIN_SECRET_LEN), treating shorter secrets as invalid placeholders.
Fail-Closed Security Model
The architecture follows a fail-closed design: any HMAC mismatch immediately terminates processing. This prevents attackers from bypassing authentication through malformed requests or signature forgery attempts.
Webhook Route Integration
The authentication function is invoked by two Next.js API routes that handle WAHA callbacks.
Global Route Handler
Located at app/api/v1/webhooks/waha/route.ts, this handler extracts the raw body and header before calling:
const auth = authenticateWahaWebhook({ rawBody, signatureHeader, sessionSecret });
if (!auth.ok) return fail(...);
Token-Scoped Route Handler
The dynamic route at app/api/v1/webhooks/waha/[token]/route.ts implements identical logic, allowing per-token webhook endpoints while maintaining the same cryptographic verification standards.
Both routes forward authentication results to the audit system, specifically logging webhook.hmac_invalid events when verification fails.
Environment Configuration
Mandatory Signature Mode
Set WAHA_WEBHOOK_REQUIRE_SIGNATURE=true in your environment to enforce strict HMAC verification. This is recommended for production deployments using WAHA Plus or other signed webhook sources.
Global Secret Configuration
The WAHA_HMAC_SECRET environment variable provides a fallback secret when session-specific secrets are not configured. For security, this should be a cryptographically random string of at least 32 bytes.
Implementation Example
To manually verify a webhook in custom middleware:
import { authenticateWahaWebhook } from "@/lib/waha/webhook-auth";
const rawBody = await request.text();
const signatureHeader = request.headers.get("x-webhook-hmac") ?? null;
const sessionSecret = await getSessionSecret(); // Decrypt from database or env
const auth = authenticateWahaWebhook({ rawBody, signatureHeader, sessionSecret });
if (!auth.ok) {
return new Response("Invalid webhook signature", { status: 401 });
}
// Access auth.signatureVerified to determine if cryptographic validation occurred
Summary
- DeskcommCRM uses SHA-512 HMAC verification via
lib/waha/webhook-auth.tsto authenticate WAHA webhooks. - The system requires the
x-webhook-hmacheader, raw request body, and a session or global secret to compute signatures. - Three security rules govern acceptance: bad signatures are rejected, missing signatures are optionally rejected based on
WAHA_WEBHOOK_REQUIRE_SIGNATURE, and valid signatures proceed withsignatureVerified: true. - Verification uses timing-safe comparison (
crypto.timingSafeEqual) and enforces a 16-byte minimum secret length. - Both global and token-scoped webhook routes (
app/api/v1/webhooks/waha/route.tsand[token]/route.ts) implement identical fail-closed authentication.
Frequently Asked Questions
What happens if the HMAC signature header is missing?
If WAHA_WEBHOOK_REQUIRE_SIGNATURE is set to "true", the request returns a 401 error with reason "signature_required". If strict mode is disabled (the default), the request proceeds but sets signatureVerified: false in the audit log, allowing compatibility with unsigned WAHA core versions while maintaining visibility.
How does DeskcommCRM prevent timing attacks during verification?
The system uses crypto.timingSafeEqual when comparing the computed SHA-512 HMAC against the x-webhook-hmac header value. This constant-time comparison prevents attackers from inferring correct signature bytes through response-time analysis.
What is the minimum secret length required for HMAC verification?
Secrets must be at least 16 bytes (MIN_SECRET_LEN) to be considered valid. Shorter secrets are treated as placeholders and ignored, effectively disabling verification for those sessions unless a valid global WAHA_HMAC_SECRET is configured.
Can HMAC verification be disabled for development or testing?
Yes. By ensuring WAHA_WEBHOOK_REQUIRE_SIGNATURE is not set to "true" (or explicitly setting it to "false"), the system accepts webhooks without signatures, marking them as unverified in logs. However, production deployments using WAHA Plus should enable strict mode to prevent event forgery.
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 →