setup-webhook

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Vapi Webhook / Server URL Setup

Vapi Webhook / 服务器URL配置

Configure server URLs to receive real-time events from Vapi during calls — transcripts, tool calls, status changes, and end-of-call reports.
Setup: Ensure
VAPI_API_KEY
is set. See the
setup-api-key
skill if needed.
配置服务器URL,以在通话期间接收来自Vapi的实时事件——包括转录文本、工具调用、状态变更及通话结束报告。
前置设置: 确保已配置
VAPI_API_KEY
。如需配置可参考
setup-api-key
技能。

Overview

概述

Vapi uses "Server URLs" (webhooks) to communicate with your application. Unlike traditional one-way webhooks, Vapi server URLs support bidirectional communication — your server can respond with data that affects the call.
Vapi使用「服务器URL」(即Webhook)与你的应用进行通信。与传统单向Webhook不同,Vapi服务器URL支持双向通信——你的服务器可返回数据以影响通话流程。

Where to Set Server URLs

服务器URL的设置位置

On an Assistant

在助手上配置

bash
curl -X PATCH https://api.vapi.ai/assistant/{id} \
  -H "Authorization: Bearer $VAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "serverUrl": "https://your-server.com/vapi/webhook",
    "serverUrlSecret": "your-webhook-secret"
  }'
bash
curl -X PATCH https://api.vapi.ai/assistant/{id} \
  -H "Authorization: Bearer $VAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "serverUrl": "https://your-server.com/vapi/webhook",
    "serverUrlSecret": "your-webhook-secret"
  }'

On a Phone Number

在电话号码上配置

bash
curl -X PATCH https://api.vapi.ai/phone-number/{id} \
  -H "Authorization: Bearer $VAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "serverUrl": "https://your-server.com/vapi/webhook"
  }'
bash
curl -X PATCH https://api.vapi.ai/phone-number/{id} \
  -H "Authorization: Bearer $VAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "serverUrl": "https://your-server.com/vapi/webhook"
  }'

At the Organization Level

组织级全局配置

Set a default server URL in the Vapi Dashboard under Settings > Server URL.
Priority order: Tool server URL > Assistant server URL > Phone Number server URL > Organization server URL.
在Vapi控制台的设置 > 服务器URL中配置默认服务器URL。
优先级顺序:工具服务器URL > 助手服务器URL > 电话号码服务器URL > 组织级服务器URL。

Event Types

事件类型

EventDescriptionExpects Response?
assistant-request
Request for dynamic assistant configYes — return assistant config
tool-calls
Assistant is calling a toolYes — return tool results
status-update
Call status changedNo
transcript
Real-time transcript updateNo
end-of-call-report
Call completed with summaryNo
hang
Assistant failed to respondNo
speech-update
Speech activity detectedNo
事件类型描述是否需要响应
assistant-request
请求动态助手配置是——返回助手配置
tool-calls
助手发起工具调用是——返回工具调用结果
status-update
通话状态变更
transcript
实时转录文本更新
end-of-call-report
通话完成并返回总结
hang
助手响应失败
speech-update
检测到语音活动

Webhook Server Example (Express.js)

Webhook服务器示例(Express.js)

typescript
import express from "express";
import crypto from "crypto";

const app = express();
app.use(express.json());

app.post("/vapi/webhook", (req, res) => {
  const { message } = req.body;

  switch (message.type) {
    case "assistant-request":
      // Dynamically configure the assistant based on the caller
      res.json({
        assistant: {
          name: "Dynamic Assistant",
          firstMessage: `Hello ${message.call.customer?.name || "there"}!`,
          model: {
            provider: "openai",
            model: "gpt-4.1",
            messages: [
              { role: "system", content: "You are a helpful assistant." },
            ],
          },
          voice: { provider: "vapi", voiceId: "Elliot" },
          transcriber: { provider: "deepgram", model: "nova-3", language: "en" },
        },
      });
      break;

    case "tool-calls":
      // Handle tool calls from the assistant
      const results = message.toolCallList.map((toolCall: any) => ({
        toolCallId: toolCall.id,
        result: handleToolCall(toolCall.name, toolCall.arguments),
      }));
      res.json({ results });
      break;

    case "end-of-call-report":
      // Process the call report
      console.log("Call ended:", {
        callId: message.call.id,
        duration: message.durationSeconds,
        cost: message.cost,
        summary: message.summary,
        transcript: message.transcript,
      });
      res.json({});
      break;

    case "status-update":
      console.log("Call status:", message.status);
      res.json({});
      break;

    case "transcript":
      console.log(`[${message.role}]: ${message.transcript}`);
      res.json({});
      break;

    default:
      res.json({});
  }
});

function handleToolCall(name: string, args: any): string {
  // Implement your tool logic here
  return `Result for ${name}`;
}

app.listen(3000, () => console.log("Webhook server running on port 3000"));
typescript
import express from "express";
import crypto from "crypto";

const app = express();
app.use(express.json());

app.post("/vapi/webhook", (req, res) => {
  const { message } = req.body;

  switch (message.type) {
    case "assistant-request":
      // 根据来电者动态配置助手
      res.json({
        assistant: {
          name: "Dynamic Assistant",
          firstMessage: `Hello ${message.call.customer?.name || "there"}!`,
          model: {
            provider: "openai",
            model: "gpt-4.1",
            messages: [
              { role: "system", content: "You are a helpful assistant." },
            ],
          },
          voice: { provider: "vapi", voiceId: "Elliot" },
          transcriber: { provider: "deepgram", model: "nova-3", language: "en" },
        },
      });
      break;

    case "tool-calls":
      // 处理助手发起的工具调用
      const results = message.toolCallList.map((toolCall: any) => ({
        toolCallId: toolCall.id,
        result: handleToolCall(toolCall.name, toolCall.arguments),
      }));
      res.json({ results });
      break;

    case "end-of-call-report":
      // 处理通话报告
      console.log("Call ended:", {
        callId: message.call.id,
        duration: message.durationSeconds,
        cost: message.cost,
        summary: message.summary,
        transcript: message.transcript,
      });
      res.json({});
      break;

    case "status-update":
      console.log("Call status:", message.status);
      res.json({});
      break;

    case "transcript":
      console.log(`[${message.role}]: ${message.transcript}`);
      res.json({});
      break;

    default:
      res.json({});
  }
});

function handleToolCall(name: string, args: any): string {
  // 在此实现你的工具逻辑
  return `Result for ${name}`;
}

app.listen(3000, () => console.log("Webhook server running on port 3000"));

Webhook Server Example (Python / Flask)

Webhook服务器示例(Python / Flask)

python
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/vapi/webhook", methods=["POST"])
def vapi_webhook():
    data = request.json
    message = data.get("message", {})
    msg_type = message.get("type")

    if msg_type == "assistant-request":
        return jsonify({
            "assistant": {
                "name": "Dynamic Assistant",
                "firstMessage": "Hello! How can I help?",
                "model": {
                    "provider": "openai",
                    "model": "gpt-4.1",
                    "messages": [
                        {"role": "system", "content": "You are a helpful assistant."}
                    ],
                },
                "voice": {"provider": "vapi", "voiceId": "Elliot"},
                "transcriber": {"provider": "deepgram", "model": "nova-3", "language": "en"},
            }
        })

    elif msg_type == "tool-calls":
        results = []
        for tool_call in message.get("toolCallList", []):
            results.append({
                "toolCallId": tool_call["id"],
                "result": f"Handled {tool_call['name']}",
            })
        return jsonify({"results": results})

    elif msg_type == "end-of-call-report":
        print(f"Call ended: {message['call']['id']}")
        print(f"Summary: {message.get('summary')}")

    return jsonify({})

if __name__ == "__main__":
    app.run(port=3000)
python
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/vapi/webhook", methods=["POST"])
def vapi_webhook():
    data = request.json
    message = data.get("message", {})
    msg_type = message.get("type")

    if msg_type == "assistant-request":
        return jsonify({
            "assistant": {
                "name": "Dynamic Assistant",
                "firstMessage": "Hello! How can I help?",
                "model": {
                    "provider": "openai",
                    "model": "gpt-4.1",
                    "messages": [
                        {"role": "system", "content": "You are a helpful assistant."}
                    ],
                },
                "voice": {"provider": "vapi", "voiceId": "Elliot"},
                "transcriber": {"provider": "deepgram", "model": "nova-3", "language": "en"},
            }
        })

    elif msg_type == "tool-calls":
        results = []
        for tool_call in message.get("toolCallList", []):
            results.append({
                "toolCallId": tool_call["id"],
                "result": f"Handled {tool_call['name']}",
            })
        return jsonify({"results": results})

    elif msg_type == "end-of-call-report":
        print(f"Call ended: {message['call']['id']}")
        print(f"Summary: {message.get('summary')}")

    return jsonify({})

if __name__ == "__main__":
    app.run(port=3000)

Webhook Authentication

Webhook身份验证

Verify webhook authenticity using the secret:
typescript
function verifyWebhook(req: express.Request, secret: string): boolean {
  const signature = req.headers["x-vapi-signature"] as string;
  if (!signature || !secret) return false;

  const payload = JSON.stringify(req.body);
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}
使用密钥验证Webhook的真实性:
typescript
function verifyWebhook(req: express.Request, secret: string): boolean {
  const signature = req.headers["x-vapi-signature"] as string;
  if (!signature || !secret) return false;

  const payload = JSON.stringify(req.body);
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Local Development

本地开发

Use the Vapi CLI to forward webhooks to your local server:
bash
undefined
使用Vapi CLI将Webhook事件转发到本地服务器:
bash
undefined

Install the CLI

安装CLI

Forward events to local server

将事件转发到本地服务器

vapi listen --forward-to localhost:3000/vapi/webhook

Or use ngrok:

```bash
ngrok http 3000
vapi listen --forward-to localhost:3000/vapi/webhook

或使用ngrok:

```bash
ngrok http 3000

Copy the ngrok URL and set it as your server URL

复制ngrok生成的URL并设置为你的服务器URL

undefined
undefined

End-of-Call Report Fields

通话结束报告字段

The
end-of-call-report
event includes:
FieldDescription
call
Full call object with metadata
transcript
Complete conversation transcript
summary
AI-generated call summary
recordingUrl
URL to the call recording
durationSeconds
Call duration
cost
Total call cost
costBreakdown
Breakdown by component (STT, LLM, TTS, transport)
messages
Array of all conversation messages
end-of-call-report
事件包含以下字段:
字段描述
call
包含元数据的完整通话对象
transcript
完整的对话转录文本
summary
AI生成的通话总结
recordingUrl
通话录音的URL
durationSeconds
通话时长
cost
通话总费用
costBreakdown
按组件细分的费用(语音转文字、大语言模型、文字转语音、传输)
messages
所有对话消息的数组

References

参考资料

Additional Resources

额外资源

This skills repository includes a Vapi documentation MCP server (
vapi-docs
) that gives your AI agent access to the full Vapi knowledge base. Use the
searchDocs
tool to look up anything beyond what this skill covers — advanced configuration, troubleshooting, SDK details, and more.
Auto-configured: If you cloned or installed these skills, the MCP server is already configured via
.mcp.json
(Claude Code),
.cursor/mcp.json
(Cursor), or
.vscode/mcp.json
(VS Code Copilot).
Manual setup: If your agent doesn't auto-detect the config, run:
bash
claude mcp add vapi-docs -- npx -y mcp-remote https://docs.vapi.ai/_mcp/server
See the README for full setup instructions across all supported agents.
本技能仓库包含一个Vapi文档MCP服务器
vapi-docs
),可让你的AI助手访问完整的Vapi知识库。使用
searchDocs
工具可查询本技能未覆盖的内容——高级配置、故障排除、SDK细节等。
自动配置: 如果你克隆或安装了这些技能,MCP服务器已通过
.mcp.json
(Claude Code)、
.cursor/mcp.json
(Cursor)或
.vscode/mcp.json
(VS Code Copilot)完成配置。
手动配置: 如果你的助手未自动检测到配置,请运行:
bash
claude mcp add vapi-docs -- npx -y mcp-remote https://docs.vapi.ai/_mcp/server
查看README获取所有支持助手的完整设置说明。