setup-webhook
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVapi 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: Ensureis set. See theVAPI_API_KEYskill if needed.setup-api-key
配置服务器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
事件类型
| Event | Description | Expects Response? |
|---|---|---|
| Request for dynamic assistant config | Yes — return assistant config |
| Assistant is calling a tool | Yes — return tool results |
| Call status changed | No |
| Real-time transcript update | No |
| Call completed with summary | No |
| Assistant failed to respond | No |
| Speech activity detected | No |
| 事件类型 | 描述 | 是否需要响应 |
|---|---|---|
| 请求动态助手配置 | 是——返回助手配置 |
| 助手发起工具调用 | 是——返回工具调用结果 |
| 通话状态变更 | 否 |
| 实时转录文本更新 | 否 |
| 通话完成并返回总结 | 否 |
| 助手响应失败 | 否 |
| 检测到语音活动 | 否 |
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
undefinedInstall the CLI
安装CLI
curl -sSL https://vapi.ai/install.sh | bash
curl -sSL https://vapi.ai/install.sh | bash
Forward events to local server
将事件转发到本地服务器
vapi listen --forward-to localhost:3000/vapi/webhook
Or use ngrok:
```bash
ngrok http 3000vapi listen --forward-to localhost:3000/vapi/webhook
或使用ngrok:
```bash
ngrok http 3000Copy the ngrok URL and set it as your server URL
复制ngrok生成的URL并设置为你的服务器URL
undefinedundefinedEnd-of-Call Report Fields
通话结束报告字段
The event includes:
end-of-call-report| Field | Description |
|---|---|
| Full call object with metadata |
| Complete conversation transcript |
| AI-generated call summary |
| URL to the call recording |
| Call duration |
| Total call cost |
| Breakdown by component (STT, LLM, TTS, transport) |
| Array of all conversation messages |
end-of-call-report| 字段 | 描述 |
|---|---|
| 包含元数据的完整通话对象 |
| 完整的对话转录文本 |
| AI生成的通话总结 |
| 通话录音的URL |
| 通话时长 |
| 通话总费用 |
| 按组件细分的费用(语音转文字、大语言模型、文字转语音、传输) |
| 所有对话消息的数组 |
References
参考资料
- Server URL Events — All event types with payload schemas
- Vapi Server URL Docs — Official documentation
- Local Development — Testing webhooks locally
- 服务器URL事件 — 所有事件类型及负载 schema
- Vapi服务器URL文档 — 官方文档
- 本地开发 — 本地测试Webhook
Additional Resources
额外资源
This skills repository includes a Vapi documentation MCP server () that gives your AI agent access to the full Vapi knowledge base. Use the tool to look up anything beyond what this skill covers — advanced configuration, troubleshooting, SDK details, and more.
vapi-docssearchDocsAuto-configured: If you cloned or installed these skills, the MCP server is already configured via (Claude Code), (Cursor), or (VS Code Copilot).
.mcp.json.cursor/mcp.json.vscode/mcp.jsonManual 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/serverSee the README for full setup instructions across all supported agents.
本技能仓库包含一个Vapi文档MCP服务器(),可让你的AI助手访问完整的Vapi知识库。使用工具可查询本技能未覆盖的内容——高级配置、故障排除、SDK细节等。
vapi-docssearchDocs自动配置: 如果你克隆或安装了这些技能,MCP服务器已通过(Claude Code)、(Cursor)或(VS Code Copilot)完成配置。
.mcp.json.cursor/mcp.json.vscode/mcp.json手动配置: 如果你的助手未自动检测到配置,请运行:
bash
claude mcp add vapi-docs -- npx -y mcp-remote https://docs.vapi.ai/_mcp/server查看README获取所有支持助手的完整设置说明。