Agent skill

WAHA Webhooks Skill

Receive and verify WAHA (WhatsApp HTTP API) webhooks. Use when setting up a WAHA webhook endpoint, verifying the X-Webhook-Hmac header (HMAC-SHA512 hex of the raw body), debugging WAHA signature verification failures, or handling events like message, message.any, message.ack, message.reaction, session.status, group.v2.join and presence.update.

Install this skill

npx skills add hookdeck/webhook-skills --skill waha-webhooks


When to Use This Skill

  • How do I receive WAHA (WhatsApp HTTP API) webhooks?
  • How do I verify the WAHA X-Webhook-Hmac signature?
  • Why is my WAHA webhook signature verification failing?
  • How do I handle message, message.ack, session.status or group.v2.join events?
  • How do I turn on HMAC for a WAHA session webhook?

Verification (core)

WAHA is self-hosted. Signing is optional and off by default: it is only sent when you set an HMAC key on the webhook (config.webhooks[].hmac.key, or WHATSAPP_HOOK_HMAC_KEY server-wide). Once set, every delivery carries:

  • X-Webhook-Hmac — hex HMAC-SHA512 of the raw request body, keyed with the key as a UTF-8 string
  • X-Webhook-Hmac-Algorithm — sha512

There is no timestamp in the signed content. Verify against the raw body, never re-serialized JSON.

const crypto = require('crypto');

function verifyWahaSignature(rawBody, signature, secret, algorithm = 'sha512') {
  if (!signature || !secret) return false;
  if (String(algorithm).toLowerCase() !== 'sha512') return false;
  const expected = crypto.createHmac('sha512', secret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(signature.toLowerCase()), Buffer.from(expected));
  } catch {
    return false; // length mismatch
  }
}

Docs test vector: body {"event":"message","session":"default","engine":"WEBJS"} with key my-secret-key gives 208f8a55dde9e05519e898b10b89bf0d0b3b0fdf11fdbf09b6b90476301b98d8097c462b2b17a6ce93b6b47a136cf2e78a33a63f6752c2c1631777076153fa89.

In Express use express.raw({ type: '*/*' }) so the raw bytes are available whatever content type WAHA sends.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Common Event Types

Every delivery is an envelope: id (evt_…), timestamp, event, session, engine, me, payload. Dispatch on event:

EventTriggered When
messageIncoming message (text, audio, files)
message.anyAny message created, including ones you sent
message.ackMessage reached server/device, was read or played (ackName: ERROR, PENDING, SERVER, DEVICE, READ, PLAYED)
message.reactionA message was reacted to (payload.reaction.text, payload.reaction.messageId)
session.statusSession status changes (STARTING, SCAN_QR_CODE, WORKING, FAILED, STOPPED, …)
group.v2.joinYou joined or were added to a group
presence.updateChat presence changes (online, offline, typing, recording, paused)

WAHA has more events (message.edited, message.revoked, group.v2.leave, poll.vote, call.received, …). See references/overview.md.

Important Headers

HeaderDescription
X-Webhook-HmacHMAC-SHA512 hex of the raw body (only when an HMAC key is set)
X-Webhook-Hmac-Algorithmsha512 (only when an HMAC key is set)
X-Webhook-Request-IdUnique id per request
X-Webhook-TimestampUnix time in milliseconds when sent

Environment Variables

WAHA_WEBHOOK_HMAC_KEY=your-secret-key   # same value as hmac.key / WHATSAPP_HOOK_HMAC_KEY in WAHA

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 waha --path /webhooks/waha

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 4, 2026

View on GitHub →