Guide to EVI’s webhooks configuration option.

EVI webhooks send structured payloads to your specified URL in real time, allowing your application to respond to key events during EVI Chat sessions. They enable you to connect EVI with your systems to monitor events, automate workflows, and gain valuable insights into user interactions.

For more information on EVI Chat sessions, check out our Chat history guide.

Supported events

The following section details each supported event, including what triggers the event, the structure of its payload, and practical use cases to help you integrate it into your workflows.

Trigger: The chat_started event is triggered when a new Chat session is started. This includes both new and resumed sessions.

Use cases

  • Workflow initiation: Use this event to trigger workflows such as starting a logging session, updating a dashboard, or notifying a team.
  • Activity monitoring: Track when new or resumed sessions occur to measure usage trends or generate real-time analytics.
  • Custom integrations: Push session start data to third-party systems (e.g., Zapier) to automate downstream actions like data collection or tracking.

Payload structure

FieldTypeDescription
event_namestringAlways "chat_started".
chat_group_idstringUnique ID of the Chat Group associated with the Chat session.
chat_idstringUnique ID of the Chat session.
config_idstringUnique ID of the EVI Config used for the session.
caller_numberstring(Optional) Phone number of the caller in E.164 format (e.g., +12223333333). This field is included only if the Chat was created via the Twilio phone calling integration.
custom_session_idstring(Optional) User-defined session ID. Relevant only when employing a custom language model in the EVI Config.
start_timeintegerNumeric Unix timestamp (in milliseconds) indicating when the session started.
chat_start_typestringIndicates if the session is new ("new_chat_group") or resumed ("resumed_chat_group").

Sample payload

Sample payload
1{
2 "event_name": "chat_started",
3 "chat_group_id": "9fc18597-3567-42d5-94d6-935bde84bf2f",
4 "chat_id": "470a49f6-1dec-4afe-8b61-035d3b2d63b0",
5 "config_id": "1b60e1a0-cc59-424a-8d2c-189d354db3f3",
6 "caller_number": null,
7 "custom_session_id": null,
8 "start_time": 1716244940648,
9 "chat_start_type": "new_chat_group"
10}

Trigger: The chat_ended event is triggered when a Chat session is ended.

Use cases

  • Analytics: Measure session durations and analyze reasons for chat termination to improve performance or user experience.
  • Workflow automations: Automatically process transcripts or save session data to external systems for further analysis or reporting.
  • Error monitoring: Track sessions that terminate with an error or timeout to identify and address recurring issues.

Payload structure

FieldTypeDescription
event_namestringAlways "chat_ended".
chat_group_idstringUnique ID of the Chat Group associated with the Chat session.
chat_idstringUnique ID of the Chat session.
config_idstringUnique ID of the EVI Config used for the session.
caller_numberstring(Optional) Phone number of the caller in E.164 format (e.g., +12223333333). This field is included only if the Chat was created via the Twilio phone calling integration.
custom_session_idstring(Optional) User-defined session ID. Relevant only when employing a custom language model in the EVI Config.
end_timeintegerUnix timestamp (in milliseconds) indicating when the session ended.
duration_secondsintegerTotal duration of the session in seconds.
end_reasonstringReason for the session’s termination (e.g., USER_ENDED, USER_TIMEOUT, MAX_DURATION_TIMEOUT, INACTIVITY_TIMEOUT, or ERROR.).

Sample payload

Sample payload
1{
2 "event_name": "chat_ended",
3 "chat_group_id": "9fc18597-3567-42d5-94d6-935bde84bf2f",
4 "chat_id": "470a49f6-1dec-4afe-8b61-035d3b2d63b0",
5 "config_id": "1b60e1a0-cc59-424a-8d2c-189d354db3f3",
6 "caller_number": null,
7 "custom_session_id": null,
8 "end_time": 1716244958546,
9 "duration_seconds": 180,
10 "end_reason": "USER_ENDED"
11}

Subscribing to events

To receive event notifications, define your webhook URL and specify the events you want to subscribe to within your EVI Config. The example below demonstrates how to configure a webhook URL for the chat_started and chat_ended events:

1curl -X POST https://api.hume.ai/v0/evi/configs \
2 -H "X-Hume-Api-Key: <YOUR_API_KEY>" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "evi_version": "2",
6 "name": "Sample Webhook Config",
7 "webhooks": [{
8 "url": <YOUR_WEBHOOK_URL>,
9 "events": ["chat_started", "chat_ended"]
10 }]
11 }'

Handling events

When EVI sends event payloads to your webhook URL, your application can process them by implementing a handler. Below are simplified example implementations in TypeScript and Python for handling chat_started and chat_ended events.

For complete implementations, check out our TypeScript Example Project and Python Example Project.

1import { WebhookEvent } from "hume/serialization/resources/empathicVoice/types/WebhookEvent";
2
3// Route to handle webhook events
4app.post("/hume-webhook", (req: Request, res: Response) => {
5 // Validate and parse using WebhookEvent
6 const event = WebhookEvent.parseOrThrow(JSON.parse(req.body));
7
8 try {
9 // Handle the specific event type
10 switch (event.eventName) {
11 case 'chat_started':
12 console.info('Processing chat_started event:', event);
13 // Add additional chat_started processing logic here
14 break;
15
16 case 'chat_ended':
17 console.info("Processing chat_ended event:", event);
18 // Add additional chat_ended processing logic here
19 break;
20
21 default:
22 console.warn(`[Event Handling] Unsupported event type: '${event.eventName}'`);
23 res.status(400).json({ error: `Unsupported event type: '${event.eventName}'` });
24 return;
25 }
26
27 res.json({ status: "success", message: `${event.event_name} processed` });
28 } catch (error) {
29 console.error("Error processing event:", error);
30 res.status(500).json({ error: "Internal server error" });
31 }
32});

Security

To ensure the authenticity and integrity of webhook payloads, EVI includes an HMAC signature and a timestamp in each request. Implementing verification safeguards your application from tampering and replay attacks.

Verifying Authenticity

Each webhook request contains the following headers:

  • X-Hume-AI-Webhook-Signature: HMAC-SHA256 signature of the payload and timestamp, signed with your Webhook Secret.
  • X-Hume-AI-Webhook-Timestamp: Unix timestamp indicating when the request was sent.

To verify authenticity:

  1. Retrieve the X-Hume-AI-Webhook-Signature and X-Hume-AI-Webhook-Timestamp headers.
  2. Concatenate the payload and timestamp, then compute the HMAC-SHA256 hash using your Webhook Secret.
  3. Compare the computed hash with the provided signature using a timing-safe comparison.
1import * as crypto from 'crypto';
2
3export function validateHmacSignature(payload: string, headers: IncomingHttpHeaders): void {
4 // Retrieve the timestamp and signature from headers
5 const timestamp = headers['x-hume-ai-webhook-timestamp'];
6 if (!timestamp) {
7 console.error('Error: Missing timestamp in the request headers.');
8 throw new Error('Missing timestamp header');
9 }
10
11 const signature = headers['x-hume-ai-webhook-signature'] as string;
12 if (!signature) {
13 console.error('Error: Missing signature in the request headers.');
14 throw new Error('Missing signature header');
15 }
16
17 // 2. Retrieve the API key from environment variables
18 const apiKey = process.env.HUME_API_KEY;
19 if (!apiKey) {
20 console.error('Error: HUME_API_KEY is not set in environment variables.');
21 throw new Error('Missing API key');
22 }
23
24 // 3. Construct the message to be hashed by concatenating the payload and the timestamp
25 const message = `${payload}.${timestamp}`;
26 const expectedSig = crypto
27 .createHmac('sha256', apiKey)
28 .update(message)
29 .digest('hex');
30
31 // 4. Compare the provided signature with the expected one using timing-safe comparison
32 const signatureBuffer = Buffer.from(signature, 'utf8');
33 const expectedSigBuffer = Buffer.from(expectedSig, 'utf8');
34 const validSignature =
35 signatureBuffer.length === expectedSigBuffer.length &&
36 crypto.timingSafeEqual(signatureBuffer, expectedSigBuffer);
37
38 // 5. If the signatures do not match, throw an error
39 if (!validSignature) {
40 console.error(`Error: Invalid HMAC signature. Expected: ${expectedSig}, Received: ${signature}`);
41 throw new Error('Invalid HMAC signature');
42 }
43
44 console.info('HMAC validation successful!');
45}
Preventing Replay Attacks

Validate the X-Hume-AI-Webhook-Timestamp header to ensure the request is recent:

  1. Check if the timestamp is within a predefined range (e.g., 3 minutes from the current time).
  2. Reject requests with timestamps outside this range.
1export function validateTimestamp(headers: IncomingHttpHeaders): void {
2 // 1. Retrieve the timestamp from the headers
3 const timestamp = headers['x-hume-ai-webhook-timestamp'] as string;
4 if (!timestamp) {
5 console.error('Error: Missing timestamp.');
6 throw new Error('Missing timestamp');
7 }
8
9 // 2. Attempt to parse the timestamp to a number
10 let timestampInt: number;
11 try {
12 timestampInt = parseInt(timestamp, 10);
13 if (isNaN(timestampInt)) {
14 // parseInt can return NaN if the string isn't a valid integer
15 throw new Error();
16 }
17 } catch (err) {
18 console.error(`Error: Invalid timestamp format: ${timestamp}`);
19 throw new Error('Invalid timestamp format');
20 }
21
22 // 3. Get the current time in seconds
23 const currentTime = Math.floor(Date.now() / 1000);
24
25 // 4. Check if the timestamp is more than 180 seconds behind the current time
26 const TIMESTAMP_VALIDATION_WINDOW = 180;
27 if (currentTime - timestampInt > TIMESTAMP_VALIDATION_WINDOW) {
28 console.error(`Error: The timestamp on the request is too old. Current time: ${currentTime}, Timestamp: ${timestamp}`);
29 throw new Error('The timestamp on the request is too old');
30 }
31
32 console.info('Timestamp validation successful!');
33}

Built with