Loading...
Loading...
Configure Vapi server URLs and webhooks to receive real-time call events, transcripts, tool calls, and end-of-call reports. Use when setting up webhook endpoints, building tool servers, or integrating Vapi events into your application.
npx skill4agent add vapiai/skills setup-webhookSetup: Ensureis set. See theVAPI_API_KEYskill if needed.setup-api-key
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"
}'| 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 |
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)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)
);
}# Install the CLI
curl -sSL https://vapi.ai/install.sh | bash
# Forward events to local server
vapi listen --forward-to localhost:3000/vapi/webhookngrok http 3000
# Copy the ngrok URL and set it as your server URLend-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 |
vapi-docssearchDocs.mcp.json.cursor/mcp.json.vscode/mcp.jsonclaude mcp add vapi-docs -- npx -y mcp-remote https://docs.vapi.ai/_mcp/server