Assistant hooks

Automate actions on call events and interruptions

Overview

Assistant hooks let you automate actions when specific events occur during a call. Use hooks to transfer calls, run functions, or send messages in response to events like call ending or speech interruptions.

Supported events include:

  • call.ending: When a call is ending
  • assistant.speech.interrupted: When the assistant’s speech is interrupted
  • customer.speech.interrupted: When the customer’s speech is interrupted
  • customer.speech.timeout: When the customer doesn’t speak within a specified time
  • assistant.transcriber.endpointedSpeechLowConfidence: When a final transcript has low confidence (below threshold but within configurable range)

You can combine actions and add filters to control when hooks trigger. Multiple customer.speech.timeout hooks can be attached to an assistant with staggered trigger delay to support different actions at different timing in the conversation.

How hooks work

Hooks are defined in the hooks array of your assistant configuration. Each hook includes:

  • on: The event that triggers the hook
  • do: The actions to perform (supports tool and say)
  • filters: (Optional) Conditions that must be met for the hook to trigger
  • options: (Optional) Configuration options for certain hook types like customer.speech.timeout and assistant.transcriber.endpointedSpeechLowConfidence
  • name: (Optional) Custom name to identify the hook

Action Types:

  • say: Speak a message. Use exact for predetermined text or prompt for AI-generated responses
  • tool: Execute a tool like transferCall, function, endCall, etc.

The call.endedReason filter can be set to any of the call ended reasons.
The transfer destination type follows the transfer call tool destinations schema.

Example: Transfer on pipeline error

Transfer a call to a fallback number if a pipeline error occurs:

1{
2 "hooks": [
3 {
4 "on": "call.ending",
5 "filters": [
6 {
7 "type": "oneOf",
8 "key": "call.endedReason",
9 "oneOf": ["pipeline-error"]
10 }
11 ],
12 "do": [
13 {
14 "type": "tool",
15 "tool": {
16 "type": "transferCall",
17 "destinations": [
18 {
19 "type": "number",
20 "number": "+1234567890",
21 "callerId": "+1987654321"
22 }
23 ]
24 }
25 }
26 ]
27 }
28 ]
29}

You can also transfer to a SIP destination:

1{
2 "hooks": [
3 {
4 "on": "call.ending",
5 "filters": [
6 {
7 "type": "oneOf",
8 "key": "call.endedReason",
9 "oneOf": ["pipeline-error"]
10 }
11 ],
12 "do": [
13 {
14 "type": "tool",
15 "tool": {
16 "type": "transferCall",
17 "destinations": [
18 {
19 "type": "sip",
20 "sipUri": "sip:user@domain.com"
21 }
22 ]
23 }
24 }
25 ]
26 }
27 ]
28}

Example: Combine actions on pipeline error

Perform multiple actions—say a message, call a function, and transfer the call—when a pipeline error occurs:

1{
2 "hooks": [
3 {
4 "on": "call.ending",
5 "filters": [
6 {
7 "type": "oneOf",
8 "key": "call.endedReason",
9 "oneOf": ["pipeline-error"]
10 }
11 ],
12 "do": [
13 {
14 "type": "say",
15 "exact": "I apologize for the technical difficulty. Let me transfer you to our support team."
16 },
17 {
18 "type": "tool",
19 "tool": {
20 "type": "function",
21 "function": {
22 "name": "log_error",
23 "parameters": {
24 "type": "object",
25 "properties": {
26 "error_type": {
27 "type": "string",
28 "value": "pipeline_error"
29 }
30 }
31 },
32 "description": "Logs the error details for monitoring"
33 },
34 "async": true,
35 "server": {
36 "url": "https://your-server.com/api"
37 }
38 }
39 },
40 {
41 "type": "tool",
42 "tool": {
43 "type": "transferCall",
44 "destinations": [
45 {
46 "type": "number",
47 "number": "+1234567890",
48 "callerId": "+1987654321"
49 }
50 ]
51 }
52 }
53 ]
54 }
55 ]
56}

Use "oneOf": ["pipeline-error"] as a catch-all filter for any pipeline-related error reason.

Example: Handle speech interruptions

Respond when the assistant’s speech is interrupted by the customer:

1{
2 "hooks": [
3 {
4 "on": "assistant.speech.interrupted",
5 "do": [
6 {
7 "type": "say",
8 "exact": ["Sorry about that", "Go ahead", "Please continue"]
9 }
10 ]
11 }
12 ]
13}

Handle customer speech interruptions in a similar way:

1{
2 "hooks": [
3 {
4 "on": "customer.speech.interrupted",
5 "do": [
6 {
7 "type": "say",
8 "exact": "I apologize for interrupting. Please continue."
9 }
10 ]
11 }
12 ]
13}

Example: Handle customer speech timeout

Respond when the customer doesn’t speak within a specified time:

1{
2 "hooks": [
3 {
4 "on": "customer.speech.timeout",
5 "options": {
6 "timeoutSeconds": 10,
7 "triggerMaxCount": 2,
8 "triggerResetMode": "onUserSpeech"
9 },
10 "do": [
11 {
12 "type": "say",
13 "prompt": "Are you still there? Please let me know how I can help you."
14 }
15 ],
16 "name": "customer_timeout_check"
17 }
18 ]
19}

The customer.speech.timeout hook supports special options:

  • timeoutSeconds: How long to wait for customer speech (1-1000 seconds, default: 7.5)
  • triggerMaxCount: Maximum times the hook triggers per call (1-10, default: 3)
  • triggerResetMode: Whether to reset the trigger count when user speaks (default: “never”)

Example: Handle low confidence transcripts

When a transcriber produces a final transcript with low confidence (below the set confidence threshold or default of 0.4), it’s normally discarded. The assistant.transcriber.endpointedSpeechLowConfidence hook allows you to handle these borderline cases by triggering actions like asking the user to repeat or logging the event.

This hook only triggers for final/endpointed transcripts that fall within a configurable confidence range. Transcripts with confidence at or above the threshold are processed normally, while those below the minimum range are still discarded.

Basic usage

Ask the user to repeat when a transcript has low confidence:

1{
2 "hooks": [
3 {
4 "on": "assistant.transcriber.endpointedSpeechLowConfidence",
5 "do": [
6 {
7 "type": "say",
8 "exact": "I'm sorry, I didn't quite catch that. Could you please repeat?"
9 }
10 ]
11 }
12 ]
13}

Using confidence options

Configure a specific confidence range for when the hook should trigger:

1{
2 "hooks": [
3 {
4 "on": "assistant.transcriber.endpointedSpeechLowConfidence",
5 "options": {
6 "confidenceMin": 0.2,
7 "confidenceMax": 0.4
8 },
9 "do": [
10 {
11 "type": "say",
12 "prompt": "You are having trouble understanding or properly hearing what the user is saying. Based on the conversation in {{transcript}}, ask the user to repeat what they just said."
13 }
14 ]
15 }
16 ]
17}

Shorthand syntax

You can use shorthand syntax similar to customer.speech.timeout hooks. The shorthand format is [confidence=min:max] where both min and max are optional:

Set both min and max:

1{
2 "hooks": [
3 {
4 "on": "assistant.transcriber.endpointedSpeechLowConfidence[confidence=0.2:0.4]",
5 "do": [
6 {
7 "type": "say",
8 "exact": "Could you please repeat that?"
9 }
10 ]
11 }
12 ]
13}

Set only minimum (max defaults to transcriber’s confidence threshold):

1{
2 "hooks": [
3 {
4 "on": "assistant.transcriber.endpointedSpeechLowConfidence[confidence=0.2:]",
5 "do": [
6 {
7 "type": "say",
8 "exact": "I didn't catch that clearly. Could you repeat?"
9 }
10 ]
11 }
12 ]
13}

Set only maximum (min defaults to max - 0.2):

1{
2 "hooks": [
3 {
4 "on": "assistant.transcriber.endpointedSpeechLowConfidence[confidence=:0.4]",
5 "do": [
6 {
7 "type": "say",
8 "exact": "Could you please speak a bit more clearly?"
9 }
10 ]
11 }
12 ]
13}

Default behavior

When no options are specified, the hook uses these defaults:

  • confidenceMax: Uses the transcriber’s confidenceThreshold (typically 0.4)
  • confidenceMin: confidenceMax - 0.2 (minimum 0)

For example, if your transcriber has a confidenceThreshold of 0.4:

  • Transcripts with confidence ≥ 0.4: Processed normally
  • Transcripts with confidence 0.2-0.4: Hook triggers
  • Transcripts with confidence < 0.2: Discarded
### Multiple hooks for different ranges
You can configure multiple hooks to handle different confidence ranges with different actions:
```json
{
"hooks": [
{
"on": "assistant.transcriber.endpointedSpeechLowConfidence[confidence=0.3:0.4]",
"do": [
{
"type": "say",
"exact": "I'm having a bit of trouble hearing you. Could you speak a bit louder?"
}
]
},
{
"on": "assistant.transcriber.endpointedSpeechLowConfidence[confidence=0.2:0.3]",
"do": [
{
"type": "say",
"exact": "I'm sorry, I really couldn't understand that. Could you please repeat what you said?"
}
]
}
]
}

The assistant.transcriber.endpointedSpeechLowConfidence hook supports these options:

  • confidenceMin: Minimum confidence threshold (0-1, default: confidenceMax - 0.2)
  • confidenceMax: Maximum confidence threshold (0-1, default: transcriber’s confidenceThreshold)

This hook is supported for transcribers that have confidenceThreshold configuration: Deepgram, Gladia, and AssemblyAI.

Example: End call if user hasn’t spoken for 30s

Assistant checks with the user at the 10 and 20s mark from when the user is silent, and ends the call after 30s of silence.

1{
2 "hooks": [
3 {
4 "hooks": [
5 {
6 "on": "customer.speech.timeout",
7 "options": {
8 "timeoutSeconds": 10,
9 "triggerMaxCount": 3,
10 "triggerResetMode": "onUserSpeech"
11 },
12 "do": [
13 {
14 "type": "say",
15 "exact": "Are you still there? Please let me know how I can help you."
16 }
17 ]
18 },
19 {
20 "on": "customer.speech.timeout",
21 "options": {
22 "timeoutSeconds": 20,
23 "triggerMaxCount": 3,
24 "triggerResetMode": "onUserSpeech"
25 },
26 "do": [
27 {
28 "type": "say",
29 "prompt": "The user has not responded in 20s. Based on the above conversation in {{transcript}} ask the user if they need help or if not you will be ending the call"
30 }
31 ]
32 }
33 ]
34 },
35 {
36 "hooks": [
37 {
38 "on": "customer.speech.timeout",
39 "options": {
40 "timeoutSeconds": 30,
41 "triggerMaxCount": 3,
42 "triggerResetMode": "onUserSpeech"
43 },
44 "do": [
45 {
46 "type": "say",
47 "exact": "I'll be ending the call now, please feel free to call back at any time."
48 },
49 {
50 "type": "tool",
51 "tool": {
52 "type": "endCall"
53 }
54 }
55 ]
56 }
57 ]
58 }
59 ]
60}

Common use cases

  • Transfer to a human agent on errors
  • Route to a fallback system if the assistant fails
  • Handle customer or assistant interruptions gracefully
  • Prompt customers who become unresponsive during a call
  • Handle low confidence transcripts by asking users to repeat or speak more clearly
  • Log errors or events for monitoring

Slack Webhook on Call Failure

You can set up automatic Slack notifications when calls fail by combining assistant hooks with Slack webhooks. This is useful for monitoring call quality and getting immediate alerts when issues occur.

Step 1: Generate a Slack webhook

Follow the Slack webhook documentation to create an incoming webhook:

  1. Create a Slack app (if you don’t have one already)
  2. Enable incoming webhooks in your app settings
  3. Create an incoming webhook for your desired channel
  4. Copy the webhook URL (it will look like https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX)

Step 2: Create a serverless function

Set up a serverless function (using a service like val.town) to convert Vapi tool call requests into Slack messages:

1export default async function(req: Request): Promise<Response> {
2 try {
3 const json = await req.json();
4 console.log(json);
5
6 const callId = json.message.call.id;
7 const reason = json.message.toolCalls[0].function.arguments.properties.callEndedReason.value;
8
9 fetch("<your-slack-webhook-url>", {
10 "method": "POST",
11 "headers": {
12 "Content-Type": "application/json",
13 },
14 body: JSON.stringify({
15 text: `🚨 Call Failed\nCall ID: ${callId}\nReason: ${reason}`
16 }),
17 });
18
19 return Response.json({
20 results: [{
21 "result": "success",
22 "toolCallId": "hook-function-call"
23 }],
24 });
25 } catch (err) {
26 console.error("JSON parsing error:", err);
27 return new Response("Invalid JSON", { status: 400 });
28 }
29}

Step 3: Configure the assistant hook

Add this hook configuration to your assistant to trigger Slack notifications on call failures:

1{
2 "hooks": [
3 {
4 "on": "call.ending",
5 "filters": [
6 {
7 "type": "oneOf",
8 "key": "call.endedReason",
9 "oneOf": ["pipeline-error"]
10 }
11 ],
12 "do": [
13 {
14 "type": "tool",
15 "tool": {
16 "type": "function",
17 "function": {
18 "name": "report_error",
19 "parameters": {
20 "type": "object",
21 "properties": {
22 "text": {
23 "type": "string",
24 "value": "A call error occurred."
25 }
26 }
27 },
28 "description": "Reports a call error to Slack."
29 },
30 "async": false,
31 "server": {
32 "url": "<your-serverless-function-url>"
33 }
34 }
35 }
36 ]
37 }
38 ]
39}

Replace <your-slack-webhook-url> with your actual Slack webhook URL and <your-serverless-function-url> with your serverless function endpoint.