← VapiCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Vapi
Snapshot Sep 30, 2026 · 23:15 UTC · version 1.2.1
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "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.",
"included_files": [
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 208
},
{
"relative_path": "references/webhook-events.md",
"size_in_bytes": 3416
}
],
"name": "setup-webhook",
"skill_md_contents": "---\nname: setup-webhook\ndescription: 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.\nlicense: MIT\n---\n\n# Vapi Webhook / Server URL Setup\n\nConfigure server URLs to receive real-time events from Vapi during calls — transcripts, tool calls, status changes, and end-of-call reports.\n\n> **Setup:** Ensure `VAPI_API_KEY` is set. See the `setup-api-key` skill if needed.\n\n## Overview\n\nVapi 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.\n\n## Where to Set Server URLs\n\n### On an Assistant\n\n```bash\ncurl -X PATCH https://api.vapi.ai/assistant/{id} \\\n -H \"Authorization: Bearer $VAPI_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"server\": {\n \"url\": \"https://your-server.com/vapi/webhook\",\n \"credentialId\": \"cred_abc123\"\n }\n }'\n```\n\nCreate the optional `credentialId` in **Dashboard > Custom Credentials**. Omit it only for a deliberately public endpoint.\n\n### On a Phone Number\n\nPhone-number updates also use a `server` object with `url` and optional `credentialId`. When updating through the API, first retrieve the number and preserve its existing `provider` discriminator in the PATCH body. The dashboard is the safest choice when the provider is unknown.\n\n### At the Organization Level\n\nSet a default server URL in the Vapi Dashboard under **Settings > Server URL**.\n\nPriority order: Tool server URL > Assistant server URL > Phone Number server URL > Organization server URL.\n\n## Event Types\n\n| Event | Description | Expects Response? |\n|-------|-------------|-------------------|\n| `assistant-request` | Request for dynamic assistant config | Yes — return assistant config |\n| `tool-calls` | Assistant is calling a tool | Yes — return tool results |\n| `status-update` | Call status changed | No |\n| `transcript` | Real-time transcript update | No |\n| `end-of-call-report` | Call completed with summary | No |\n| `hang` | Assistant failed to respond | No |\n| `speech-update` | Speech activity detected | No |\n\n## Webhook Server Example (Express.js)\n\n```typescript\nimport express from \"express\";\nconst app = express();\napp.use(express.json());\n\napp.post(\"/vapi/webhook\", (req, res) => {\n const { message } = req.body;\n\n switch (message.type) {\n case \"assistant-request\":\n // Dynamically configure the assistant based on the caller\n res.json({\n assistant: {\n name: \"Dynamic Assistant\",\n firstMessage: `Hello ${message.call.customer?.name || \"there\"}!`,\n model: {\n provider: \"openai\",\n model: \"gpt-4.1\",\n messages: [\n { role: \"system\", content: \"You are a helpful assistant.\" },\n ],\n },\n voice: { provider: \"vapi\", voiceId: \"Elliot\", version: 2 },\n transcriber: { provider: \"deepgram\", model: \"nova-3\", language: \"en\" },\n },\n });\n break;\n\n case \"tool-calls\":\n // Handle tool calls from the assistant\n const results = (message.toolCallList || []).map((toolCall: any) => ({\n toolCallId: toolCall.id,\n result: handleToolCall(\n toolCall.name,\n toolCall.parameters || toolCall.arguments\n ),\n }));\n res.json({ results });\n break;\n\n case \"end-of-call-report\":\n // Process the call report\n console.log(\"Call ended:\", {\n callId: message.call.id,\n endedReason: message.endedReason,\n cost: message.cost,\n analysis: message.analysis,\n artifact: message.artifact,\n });\n res.json({});\n break;\n\n case \"status-update\":\n console.log(\"Call status:\", message.status);\n res.json({});\n break;\n\n case \"transcript\":\n console.log(`[${message.role}]: ${message.transcript}`);\n res.json({});\n break;\n\n default:\n res.json({});\n }\n});\n\nfunction handleToolCall(name: string, args: any): string {\n // Implement your tool logic here\n return `Result for ${name}`;\n}\n\napp.listen(3000, () => console.log(\"Webhook server running on port 3000\"));\n```\n\n## Webhook Server Example (Python / Flask)\n\n```python\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n@app.route(\"/vapi/webhook\", methods=[\"POST\"])\ndef vapi_webhook():\n data = request.json\n message = data.get(\"message\", {})\n msg_type = message.get(\"type\")\n\n if msg_type == \"assistant-request\":\n return jsonify({\n \"assistant\": {\n \"name\": \"Dynamic Assistant\",\n \"firstMessage\": \"Hello! How can I help?\",\n \"model\": {\n \"provider\": \"openai\",\n \"model\": \"gpt-4.1\",\n \"messages\": [\n {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"}\n ],\n },\n \"voice\": {\"provider\": \"vapi\", \"voiceId\": \"Elliot\", \"version\": 2},\n \"transcriber\": {\"provider\": \"deepgram\", \"model\": \"nova-3\", \"language\": \"en\"},\n }\n })\n\n elif msg_type == \"tool-calls\":\n results = []\n for tool_call in message.get(\"toolCallList\", []):\n results.append({\n \"toolCallId\": tool_call[\"id\"],\n \"result\": f\"Handled {tool_call['name']}\",\n })\n return jsonify({\"results\": results})\n\n elif msg_type == \"end-of-call-report\":\n print(f\"Call ended: {message['call']['id']}\")\n print(f\"Summary: {message.get('analysis', {}).get('summary')}\")\n\n return jsonify({})\n\nif __name__ == \"__main__\":\n app.run(port=3000)\n```\n\n## Webhook Authentication\n\nUse a Vapi Custom Credential and place its ID in `server.credentialId`. Vapi supports Bearer Token, OAuth 2.0 client credentials, and HMAC credentials. Verify requests according to the credential you configured; HMAC header names, algorithms, timestamps, and payload formats are configurable, so do not assume a fixed `x-vapi-signature` format.\n\nFor a bearer credential, compare the incoming `Authorization: Bearer <token>` value with the token stored securely by your server. Never put the secret itself in an assistant or phone-number payload.\n\n## Local Development\n\nUse the Vapi CLI with a public tunnel. The CLI forwards from port 4242 to your app, but it does not create the public URL itself:\n\n```bash\n# Install the CLI\ncurl -sSL https://vapi.ai/install.sh | bash\n\n# Terminal 1: expose the CLI listener\nngrok http 4242\n\n# Terminal 2: forward events from the CLI to your app\nvapi listen --forward-to localhost:3000/vapi/webhook\n```\n\nSet the Vapi server URL to the public ngrok URL. To skip the CLI and tunnel directly to the Express or Flask server instead:\n\n```bash\nngrok http 3000\n# Copy the ngrok URL and set it as your server URL\n```\n\n## End-of-Call Report Fields\n\nThe `end-of-call-report` event includes:\n\n| Field | Description |\n|-------|-------------|\n| `call` | Full call object with metadata |\n| `endedReason` | Why the call ended |\n| `artifact` | Recording, transcript, messages, and other enabled artifacts |\n| `analysis` | Configured summaries, structured data, and success evaluation |\n| `cost` | Total call cost |\n| `startedAt` / `endedAt` | Call timing, when included |\n\n## References\n\n- [Common Server URL Events](references/webhook-events.md) — Common event payloads and responses\n- [Setting Server URLs](https://docs.vapi.ai/server-url/setting-server-urls) — Placement and priority\n- [Server Authentication](https://docs.vapi.ai/server-url/server-authentication) — Custom Credentials\n- [Local Development](https://docs.vapi.ai/server-url/developing-locally) — Testing webhooks locally\n\n## Additional Resources\n\nVapi provides a **documentation MCP server** that gives compatible AI agents access to the Vapi knowledge base. Use its documentation search for advanced configuration, troubleshooting, SDK details, and anything beyond this skill.\n\n\nSee the [Vapi MCP integration guide](https://docs.vapi.ai/cli/mcp) for setup instructions across supported agents.\n"
}SHA-256 of public snapshot: d01c2eb953ff385cdeeb24721b6bb90f3c692f32c5cebf9bc87dd91f6edcdeed