# How DeskcommCRM Secures WAHA Webhooks with HMAC Verification

> Learn how DeskcommCRM secures WAHA webhooks with SHA-512 HMAC verification for robust cryptographic authentication. Explore the webhook-auth.ts file and configure strict mode.

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

---

**DeskcommCRM validates every inbound WAHA webhook using SHA-512 HMAC verification in [`lib/waha/webhook-auth.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/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`](https://github.com/melgarafael/DeskcommCRM/blob/main/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`](https://github.com/melgarafael/DeskcommCRM/blob/main/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 the `x-webhook-hmac` (case-insensitive) HTTP header.
- **`sessionSecret`** – The decrypted secret for the specific WAHA session, or the global fallback `WAHA_HMAC_SECRET` environment variable.

### The Three Security Rules (Lines 13–27)

The implementation applies these rules in order:

1. **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.
2. **Signature Required Mode** – When `WAHA_WEBHOOK_REQUIRE_SIGNATURE` is set to `"true"`, missing or empty headers trigger rejection with `{ ok: false, reason: "signature_required" }`.
3. **Optional Verification** – If the header is absent and strict mode is disabled, the request proceeds with `signatureVerified: false` logged 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`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/waha/ingest.ts). This function:

- Recomputes the SHA-512 HMAC of the raw body using the provided secret.
- Compares the result using `crypto.timingSafeEqual` to 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`](https://github.com/melgarafael/DeskcommCRM/blob/main/app/api/v1/webhooks/waha/route.ts), this handler extracts the raw body and header before calling:

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

```typescript
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.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/waha/webhook-auth.ts) to authenticate WAHA webhooks.
- The system requires the **`x-webhook-hmac`** header, **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 with `signatureVerified: 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.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/app/api/v1/webhooks/waha/route.ts) and `[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.