setup-webhook
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.
What this skill does
# Vapi Webhook / Server URL Setup
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.
## 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.
## Where to Set Server URLs
### 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"
}'
```
### 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"
}'
```
### 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.
## Event Types
| 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 | No |
| `hang` | Assistant failed to respond | No |
| `speech-update` | Speech activity detected | No |
## Webhook Server Example (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"));
```
## Webhook Server Example (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)
```
## Webhook Authentication
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)
);
}
```
## Local Development
Use the Vapi CLI to forward webhooks to your local server:
```bash
# 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:
```bash
ngrok http 3000
# Copy the ngrok URL and set it as your server URL
```
## End-of-Call Report Fields
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 |
| `costBreakdown` | Breakdown by component (STT, LLM, TTS, transport) |
| `messages` | Array of all conversation messages |
## References
- [Server URL Events](references/webhook-events.md) — All event types with payload schemas
- [Vapi Server URL Docs](https://docs.vapi.ai/server-url) — Official documentation
- [Local Development](https://docs.vapi.ai/server-url/developing-locally) — Testing webhooks locally
## 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](../README.md#vapi-documentation-server-mcp) for full setup instructions across all supported agents.
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.