Agent skill
Hume Webhooks Skill
Receive and verify Hume EVI (speech-to-speech) webhooks. Use when setting up a Hume webhook handler, debugging X-Hume-AI-Webhook-Signature HMAC-SHA256 signature verification, or handling chat_started, chat_ended and tool_call events from Hume's emotionally intelligent voice AI.
Install this skill
npx skills add hookdeck/webhook-skills --skill hume-webhooks
Webhooks from Hume AI (hume.ai), the emotionally intelligent voice AI company. This skill covers EVI (speech-to-speech) webhooks only, which are configured on an EVI Config and report the lifecycle of EVI Chat sessions.
Setup Note
For Hume EVI (speech-to-speech) webhooks. Paste the webhook signing key from the Developers page.
When to Use This Skill
- How do I receive Hume EVI webhooks?
- How do I verify the
X-Hume-AI-Webhook-Signatureheader? - Why is my Hume webhook signature verification failing?
- How do I handle
chat_started,chat_endedandtool_callevents? - How do I subscribe an EVI Config to webhook events?
Verification (core)
Hume signs each request with two headers:
X-Hume-AI-Webhook-Signature: <hex digest>
X-Hume-AI-Webhook-Timestamp: <unix epoch seconds>
The digest is HMAC-SHA256(signing key, "<raw body>.<timestamp>"), lowercase hex, sent bare with no prefix. Note the order: body first, then a period, then the timestamp. The key is the signing key's UTF-8 bytes, used as-is. Build the signed message as bytes: the raw body, a literal ., then the timestamp header exactly as received. Never decode and re-encode the body, and never JSON.parse / JSON.stringify before verifying.
const crypto = require('crypto');
function verifyHumeSignature(rawBody /* Buffer */, signatureHeader, timestampHeader, secret) {
if (!secret || !signatureHeader || !timestampHeader) return false;
// The whole header is one hex digest; validate format BEFORE decoding
if (!/^[0-9a-fA-F]{64}$/.test(signatureHeader)) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(Buffer.concat([rawBody, Buffer.from('.'), Buffer.from(timestampHeader, 'latin1')]))
.digest();
const provided = Buffer.from(signatureHeader.toLowerCase(), 'hex'); // hex is case-insensitive
return provided.length === expected.length && crypto.timingSafeEqual(provided, expected);
}
Missing or empty signature or timestamp header, malformed value, or mismatch: respond 401.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Hume's docs show manual verification code and no verification helper in an SDK, so the examples implement the documented algorithm directly. The signing key is the account's webhook signing key: Hume platform → Developers page → Generate signing key.
Timestamp tolerance is opt-in
Hume's own example rejects deliveries more than 180 seconds old. This skill does not by default: Hookdeck retries and replays resend the original headers and timestamp, and Hookdeck applies no tolerance itself, so a fixed window would reject legitimate retries. Set HUME_ to opt in (X-Hume-AI-Webhook-Timestamp is Unix seconds; unset or 0 = off). Because the timestamp is signed, it cannot be swapped for a fresh one.
Event Types
Dispatch on the body's event_name field.
event_name | Triggered When | Key fields |
|---|---|---|
chat_started | A Chat session starts (new or resumed) | chat_id, chat_group_id, config_id, start_time (ms), chat_start_type (new_chat_group or resumed_chat_group) |
chat_ended | A Chat session ends | end_time (ms), duration_seconds, end_reason (e.g. USER_ENDED, USER_TIMEOUT, MAX_DURATION_TIMEOUT, INACTIVITY_TIMEOUT, ERROR) |
tool_call | The model calls a tool during a Chat | timestamp (ms), tool_call_message with name, parameters (stringified JSON), response_required, tool_call_id, tool_type (builtin or function) |
Every payload also carries chat_group_id, chat_id, config_id, and optional caller_number (E.164, Twilio phone calls only) and custom_session_id (custom language models only). Timestamps inside payloads are milliseconds; the header timestamp is seconds.
Log and acknowledge any other event_name rather than failing.
Important Details
- Method:
POST. Hookdeck's Hume source manages the method as POST. - Subscribing: add a
webhooksentry to an EVI Config:{ "url": "...", "events": ["chat_. Only subscribed events are sent.started", "chat_ ended", "tool_ call"] } - Rotating the key: generating a new signing key immediately invalidates the old one. Only admins can do it in an organization.
tool_callreplies: ifresponse_requiredis true, send the tool response (or a tool error) back to EVI through the Control Plane API; the webhook response body is not used for it.- Retries: Hume's webhook docs do not describe a retry or timeout policy. Respond quickly and process asynchronously.
- No delivery ID header. Build an idempotency key yourself, e.g.
chat_id+event_name(+tool_call_idfor tool calls).
Environment Variables
HUME_WEBHOOK_SECRET=your_webhook_signing_key # Developers page > Generate signing key
HUME_WEBHOOK_TOLERANCE_SECONDS=0 # optional; unset or 0 = no timestamp window (default)
Hume's docs call the variable HUME_WEBHOOK_SIGNING_KEY; this skill uses HUME_WEBHOOK_SECRET throughout.
Local Development
# Forward Hume webhooks to your local server (no account required)
npx hookdeck-cli listen 3000 hume --path /webhooks/hume
Put the resulting HTTPS URL in the webhooks array of your EVI Config, then start a Chat that uses that Config.
Reference Materials
- references/overview.md - Hume EVI webhook concepts, events, payload fields
- references/setup.md - Generate the signing key and subscribe an EVI Config
- references/verification.md -
X-Hume-AI-Webhook-Signatureverification details and gotchas