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.

**Looking for example code?** See example projects in TypeScript and Python on GitHub:

\
\
TypeScript Example\
\
See EVI WebHooks implemented in TypeScript.](https://github.com/HumeAI/hume-api-examples/blob/main/evi/evi-typescript-webhooks/README.md) \
\
Python Example\
\
See EVI WebHooks implemented in Python.](https://github.com/HumeAI/hume-api-examples/blob/main/evi/evi-python-webhooks/README.md)

## 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.

### Chat started

#### 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

| Field | Type | Description |
| --- | --- | --- |
| `event_name` | `string` | Always `"chat_started"`. |
| `chat_group_id` | `string` | Unique ID of the **Chat Group** associated with the **Chat** session. |
| `chat_id` | `string` | Unique ID of the **Chat** session. |
| `config_id` | `string` | Unique ID of the EVI **Config** used for the session. |
| `caller_number` | `string` | _(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](https://dev.hume.ai/docs/speech-to-speech-evi/guides/phone-calling) integration. |
| `custom_session_id` | `string` | _(Optional)_ User-defined session ID. Relevant only when employing a [custom language model](https://dev.hume.ai/docs/speech-to-speech-evi/guides/custom-language-model) in the EVI Config. |
| `start_time` | `integer` | Numeric Unix timestamp (in milliseconds) indicating when the session started. |
| `chat_start_type` | `string` | Indicates if the session is new (`"new_chat_group"`) or resumed (`"resumed_chat_group"`). |

#### Sample payload

Sample payload

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

### Chat ended

#### 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

| Field | Type | Description |
| --- | --- | --- |
| `event_name` | `string` | Always `"chat_ended"`. |
| `chat_group_id` | `string` | Unique ID of the **Chat Group** associated with the **Chat** session. |
| `chat_id` | `string` | Unique ID of the **Chat** session. |
| `config_id` | `string` | Unique ID of the EVI **Config** used for the session. |
| `caller_number` | `string` | _(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](https://dev.hume.ai/docs/speech-to-speech-evi/guides/phone-calling) integration. |
| `custom_session_id` | `string` | _(Optional)_ User-defined session ID. Relevant only when employing a [custom language model](https://dev.hume.ai/docs/speech-to-speech-evi/guides/custom-language-model) in the EVI Config. |
| `end_time` | `integer` | Numeric Unix timestamp (in milliseconds) indicating when the session ended. |
| `duration_seconds` | `integer` | Total duration of the session in seconds. |
| `end_reason` | `string` | Reason for the session’s termination (e.g., `USER_ENDED`, `USER_TIMEOUT`, `MAX_DURATION_TIMEOUT`, `INACTIVITY_TIMEOUT`, or `ERROR`). |

#### Sample payload

Sample payload

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

### Tool call

#### Trigger

The `tool_call` event is triggered when a tool call is made during a **Chat** session.

#### Use cases

- **Server-side tool use**: Invoke your tools server-side and send tool responses to EVI via the [Control Plane API](https://dev.hume.ai/docs/speech-to-speech-evi/guides/control-plane).
- **Analytics**: Track tool calls to measure usage patterns and identify popular tools.
- **Error monitoring**: Track tool calls that fail to complete to identify and address recurring issues.

#### Payload structure

| Field | Type | Description |
| --- | --- | --- |
| `event_name` | `string` | Always `"tool_call"`. |
| `chat_group_id` | `string` | Unique ID of the **Chat Group** associated with the **Chat** session. |
| `chat_id` | `string` | Unique ID of the **Chat** session. |
| `config_id` | `string` | Unique ID of the EVI **Config** used for the session. |
| `caller_number` | `string` | _(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](https://dev.hume.ai/docs/speech-to-speech-evi/guides/phone-calling) integration. |
| `custom_session_id` | `string` | _(Optional)_ User-defined session ID. Relevant only when employing a [custom language model](https://dev.hume.ai/docs/speech-to-speech-evi/guides/custom-language-model) in the EVI Config. |
| `timestamp` | `integer` | Numeric Unix timestamp (in milliseconds) indicating when the tool call message was sent. |
| `tool_call_message[name]` | `string` | Name of the tool call’s corresponding tool. |
| `tool_call_message[parameters]` | `string` | Parameters of the tool call. Is a stringified JSON schema. |
| `tool_call_message[response_required]` | `boolean` | Indicates whether a response to the tool call is required from the developer, either in the form of a Tool Response message or a Tool Error message. |
| `tool_call_message[tool_call_id]` | `string` | The unique identifier for the specific tool call instance. |
| `tool_call_message[tool_type]` | `enum` | Type of tool called. Either `builtin` for natively implemented tools, like web search, or `function` for user-defined tools. |

#### Sample payload

Sample payload

```
{
  "event_name": "tool_call",
  "chat_group_id": "9fc18597-3567-42d5-94d6-935bde84bf2f",
  "chat_id": "470a49f6-1dec-4afe-8b61-035d3b2d63b0",
  "config_id": "1b60e1a0-cc59-424a-8d2c-189d354db3f3",
  "caller_number": null,
  "custom_session_id": "string",
  "timestamp": 1716244958546,
  "tool_call_message": {
    "name": "get_current_weather",
    "parameters": "{\"format\": \"fahrenheit\", \"location\": \"San Francisco, CA\"}",
    "response_required": true,
    "tool_call_id": "d20827af-5d8d-4f66-b6b9-ce2e3e1ea2b2",
    "tool_type": "function"
  }
}
```

## Subscribing to events

To receive event notifications, define your webhook URL and specify the events you want to subscribe to within your [EVI Config](https://dev.hume.ai/reference/speech-to-speech-evi/configs/create-config#request.body.webhooks). The example below demonstrates how to configure a webhook URL for the `chat_started` and `chat_ended` events:

```
curl https://api.hume.ai/v0/evi/configs \
  -H "X-Hume-Api-Key: <YOUR_API_KEY>" \
  --json '{\
    "evi_version": "3",\
    "name": "Sample Webhook Config",\
    "webhooks": [{\
      "url": <YOUR_WEBHOOK_URL>,\
      "events": ["chat_started", "chat_ended", "tool_call"]\
    }]\
  }'
```

## 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.

```
import type { WebhookEvent } from "hume/serialization/resources/empathicVoice/types/WebhookEvent";

// Route to handle webhook events
app.post("/hume-webhook", (req: Request, res: Response) => {
  // Validate and parse using WebhookEvent
  const event = WebhookEvent.parseOrThrow(JSON.parse(req.body));

try {
    // Handle the specific event type
    switch (event.eventName) {
      case 'chat_started':
        console.info('Processing chat_started event:', event);
        // Add additional chat_started processing logic here
        break;

case 'chat_ended':
        console.info("Processing chat_ended event:", event);
        // Add additional chat_ended processing logic here
        break;

case 'tool_call':
        console.info("Processing tool_call event:", event);
        // Add additional tool_call processing logic here
        break;

default:
        res.status(400).json({
          error: `Unsupported event type: '${event.eventName}'`
        });
        return;
    }

res.json({
      status: "success",
      message: `${event.event_name} processed`
    });
  } catch (error) {
    console.error("Error processing event:", error);
    res.status(500).json({ error: "Internal server error" });
  }
});
```

### 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.

#### Webhook signing key

Each webhook payload is signed with a dedicated webhook signing key. The signing key is a stable, per-account secret that does not change when you rotate your API key or switch authentication methods.

To provision your webhook signing key:

1. Navigate to the [Developers page](https://app.hume.ai/settings/keys) on the Hume platform.
2. Click **Generate signing key**.
3. Copy the key and store it securely. You will use this key to verify incoming webhook payloads.

**If you generate a new signing key, the previous key is immediately invalidated.** Any webhook payloads signed with the old key will fail verification. For organizations, only admins can generate and rotate the webhook signing key.

#### Verifying authenticity

Each webhook request contains the following headers:

- `X-Hume-AI-Webhook-Signature`: HMAC-SHA256 signature of the payload and timestamp, signed using your webhook signing key.
- `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 signing key.
3. Compare the computed hash with the provided signature using a timing-safe comparison.

#### 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.

#### Example

The following example combines both signature verification and timestamp validation into a single function:

```
import * as crypto from 'crypto';
import { IncomingHttpHeaders } from 'http';

export function validateWebhookHeaders(
  payload: string,
  headers: IncomingHttpHeaders,
): void {
  // Extract required headers
  const timestamp = headers['x-hume-ai-webhook-timestamp'] as string;
  if (!timestamp) {
    throw new Error('Missing timestamp header');
  }

const signature = headers['x-hume-ai-webhook-signature'] as string;
  if (!signature) {
    throw new Error('Missing signature header');
  }

// Validate HMAC signature using the webhook signing key
  const signingKey = process.env.HUME_WEBHOOK_SIGNING_KEY;
  if (!signingKey) {
    throw new Error('HUME_WEBHOOK_SIGNING_KEY is not set in environment variables');
  }

const message = `${payload}.${timestamp}`;
  const expectedSig = crypto
    .createHmac('sha256', signingKey)
    .update(message)
    .digest('hex');

const signatureBuffer = Buffer.from(signature, 'utf8');
  const expectedSigBuffer = Buffer.from(expectedSig, 'utf8');
  const validSignature =
    signatureBuffer.length === expectedSigBuffer.length &&
    crypto.timingSafeEqual(signatureBuffer, expectedSigBuffer);

if (!validSignature) {
    throw new Error('Invalid HMAC signature');
  }

// Validate timestamp to prevent replay attacks
  const timestampInt = parseInt(timestamp, 10);
  if (isNaN(timestampInt)) {
    throw new Error('Invalid timestamp format');
  }

const currentTime = Math.floor(Date.now() / 1000);
  const TIMESTAMP_VALIDATION_WINDOW = 180;
  if (currentTime - timestampInt > TIMESTAMP_VALIDATION_WINDOW) {
    throw new Error('The timestamp on the request is too old');
  }
}
```
