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-Hmacsignature? - Why is my WAHA webhook signature verification failing?
- How do I handle
message,message.ack,session.statusorgroup.v2.joinevents? - 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 stringX-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:
| Event | Triggered When |
|---|---|
message | Incoming message (text, audio, files) |
message.any | Any message created, including ones you sent |
message.ack | Message reached server/device, was read or played (ackName: ERROR, PENDING, SERVER, DEVICE, READ, PLAYED) |
message.reaction | A message was reacted to (payload.reaction.text, payload.reaction.messageId) |
session.status | Session status changes (STARTING, SCAN_QR_CODE, WORKING, FAILED, STOPPED, …) |
group.v2.join | You joined or were added to a group |
presence.update | Chat 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
| Header | Description |
|---|---|
X-Webhook-Hmac | HMAC-SHA512 hex of the raw body (only when an HMAC key is set) |
X-Webhook-Hmac-Algorithm | sha512 (only when an HMAC key is set) |
X-Webhook-Request-Id | Unique id per request |
X-Webhook-Timestamp | Unix 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
- references/overview.md - WAHA webhooks, event list, payload envelope, retries
- references/setup.md - Configure the webhook URL, events, HMAC key and retries
- references/verification.md - X-Webhook-Hmac details, gotchas, debugging