npx skills add https://github.com/vapiai/skills --skill setup-webhook配置服务器 URL 以在通话期间接收来自 Vapi 的实时事件——包括转录文本、工具调用、状态变更和通话结束报告。
设置: 确保已设置
VAPI_API_KEY。如有需要,请参考setup-api-key技能。
Vapi 使用“服务器 URL”(webhook)与您的应用程序通信。与传统的单向 webhook 不同,Vapi 服务器 URL 支持双向通信——您的服务器可以返回影响通话的数据。
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"
}'
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"
}'
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
在 Vapi 仪表板的 设置 > 服务器 URL 下设置默认服务器 URL。
优先级顺序:工具服务器 URL > 助理服务器 URL > 电话号码服务器 URL > 组织服务器 URL。
| 事件 | 描述 | 需要响应? |
|---|---|---|
assistant-request | 请求动态助理配置 | 是——返回助理配置 |
tool-calls | 助理正在调用工具 | 是——返回工具结果 |
status-update | 通话状态已变更 | 否 |
transcript | 实时转录更新 | 否 |
end-of-call-report | 通话完成并附带摘要 | 否 |
hang | 助理未能响应 | 否 |
speech-update | 检测到语音活动 | 否 |
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"));
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 的真实性:
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)
);
}
使用 Vapi CLI 将 webhook 转发到您的本地服务器:
# 安装 CLI
curl -sSL https://vapi.ai/install.sh | bash
# 将事件转发到本地服务器
vapi listen --forward-to localhost:3000/vapi/webhook
或使用 ngrok:
ngrok http 3000
# 复制 ngrok URL 并将其设置为您的服务器 URL
end-of-call-report 事件包含:
| 字段 | 描述 |
|---|---|
call | 包含元数据的完整通话对象 |
transcript | 完整的对话转录文本 |
summary | AI 生成的通话摘要 |
recordingUrl | 通话录音的 URL |
durationSeconds | 通话时长 |
cost | 通话总成本 |
costBreakdown | 按组件(STT、LLM、TTS、传输)细分的成本 |
messages | 所有对话消息的数组 |
此技能仓库包含一个 Vapi 文档 MCP 服务器 (vapi-docs),它让您的 AI 代理可以访问完整的 Vapi 知识库。使用 searchDocs 工具来查找本技能未涵盖的任何内容——高级配置、故障排除、SDK 详细信息等。
自动配置: 如果您克隆或安装了这些技能,MCP 服务器已通过 .mcp.json (Claude Code)、.cursor/mcp.json (Cursor) 或 .vscode/mcp.json (VS Code Copilot) 完成配置。
手动设置: 如果您的代理未自动检测到配置,请运行:
claude mcp add vapi-docs -- npx -y mcp-remote https://docs.vapi.ai/_mcp/server
有关在所有支持的代理上进行完整设置说明,请参阅 README。
每周安装次数
291
仓库
GitHub 星标数
25
首次出现
2026年2月23日
安全审计
安装于
cursor261
gemini-cli256
github-copilot256
amp256
codex256
opencode256
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_KEYis set. See thesetup-api-keyskill if needed.
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.
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"
}'
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"
}'
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.
| Event | Description | Expects Response? |
|---|---|---|
assistant-request | Request for dynamic assistant config | Yes — return assistant config |
tool-calls | Assistant is calling a tool | Yes — return tool results |
status-update | Call status changed | No |
transcript | Real-time transcript update | No |
end-of-call-report | Call completed with summary |
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"));
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)
Verify webhook authenticity using the secret:
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)
);
}
Use the Vapi CLI to forward webhooks to your local server:
# Install the CLI
curl -sSL https://vapi.ai/install.sh | bash
# Forward events to local server
vapi listen --forward-to localhost:3000/vapi/webhook
Or use ngrok:
ngrok http 3000
# Copy the ngrok URL and set it as your server URL
The end-of-call-report event includes:
| Field | Description |
|---|---|
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 |
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:
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.
Weekly Installs
291
Repository
GitHub Stars
25
First Seen
Feb 23, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykWarn
Installed on
cursor261
gemini-cli256
github-copilot256
amp256
codex256
opencode256
AI Elements:基于shadcn/ui的AI原生应用组件库,快速构建对话界面
56,200 周安装
| No |
hang | Assistant failed to respond | No |
speech-update | Speech activity detected | No |
costBreakdown |
| Breakdown by component (STT, LLM, TTS, transport) |
messages | Array of all conversation messages |