Build a Voice Agent with VAPI Functions and Custom Tools (2026)
By Sagar Shankaran, Founder of CallSphere
VAPI deprecated 'Custom Functions' for the Tools API. Wire a webhook tool, secure with HMAC, and ship a working appointment-booker — code + pitfalls.
Key takeaways
TL;DR — VAPI's "Custom Functions" feature was deprecated in 2026 in favor of the Tools API. Tools are reusable across assistants, support async webhooks, and have a clean HMAC-signed contract. This is the only correct way to extend a VAPI agent today.
What you'll build
A VAPI assistant that calls a book_appointment tool against your Next.js webhook, validates the HMAC signature, books the slot in Postgres, and returns a confirmation the assistant speaks aloud.
Architecture
flowchart LR
CL[Caller] --> VP[VAPI assistant]
VP -- function_call --> TL[Tool: book_appointment]
TL -- HMAC POST --> WH[Your /api/vapi/book webhook]
WH -- result JSON --> VP --> CL
Step 1 — Create the tool in dashboard
In dash.vapi.ai → Tools → Create. Pick Custom Tool and fill:
- Name:
book_appointment - Server URL:
https://yourhost.com/api/vapi/book - Description: "Book a slot once the user confirms an ISO-8601 date+time."
- Parameters JSON:
```json { "type": "object", "properties": { "iso": { "type": "string", "description": "ISO-8601 datetime" }, "name": { "type": "string" } }, "required": ["iso", "name"] } ```
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
Step 2 — Attach to assistant
In Assistants → Edit → Tools, toggle book_appointment on. The system prompt should mention "Use book_appointment once the caller confirms."
Step 3 — Webhook handler (Next.js)
```ts // app/api/vapi/book/route.ts import crypto from "crypto"; import { db } from "@/lib/db";
export async function POST(req: Request) {
const raw = await req.text();
const sig = req.headers.get("x-vapi-signature") ?? "";
const expected = crypto.createHmac("sha256", process.env.VAPI_WEBHOOK_SECRET!)
.update(raw).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return new Response("bad sig", { status: 401 });
}
const body = JSON.parse(raw);
const call = body.message.toolCallList[0];
const { iso, name } = call.function.arguments;
await db.appt.create({ data: { iso, name } });
return Response.json({
results: [{
toolCallId: call.id,
result: Booked ${name} for ${iso}.,
}],
});
}
```
Step 4 — Programmatic create
```ts
const r = await fetch("https://api.vapi.ai/assistant", {
method: "POST",
headers: { Authorization: Bearer ${process.env.VAPI_API_KEY},
"Content-Type": "application/json" },
body: JSON.stringify({
name: "Clinic Concierge",
model: { provider: "openai", model: "gpt-4o", toolIds: ["tool_book_appointment"] },
voice: { provider: "11labs", voiceId: "rachel" },
transcriber: { provider: "deepgram", model: "nova-3" },
firstMessage: "Hi — Sunrise Clinic, how can I help?",
}),
});
```
Step 5 — Async tools
For long-running work (>5s), set async: true on the tool. VAPI keeps the user engaged with a fill phrase ("One sec while I check that...") and waits up to 60s for your webhook to POST a result back via the /call/:id/control endpoint.
Step 6 — Test with the simulator
vapi-cli simulate --assistant <id> --transcript 'I want 3pm tomorrow' exercises the tool path without spending phone minutes.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
Pitfalls
toolCallIdmismatch: Your responseresults[].toolCallIdMUST match the requestcall.id— otherwise VAPI ignores the result.- HMAC vs server JWT: Some old docs reference Bearer tokens — Tools use HMAC by default in 2026.
- Idempotency: VAPI retries on 5xx — return
200even on duplicate, with the same result body. - Tool description quality: One job per tool, plain-English description; the LLM picks tools by description, not name.
How CallSphere does this
Explore a live demo and compare current plans to find the right fit for your business.
FAQ
Default vs custom tools? VAPI ships defaults like endCall, transferCall, dtmf — use them for control flow and reserve custom tools for business logic.
Latency? Tool execution adds your webhook RTT; keep p95 under 800ms or VAPI fills with a "still checking" line.
Streaming responses? Tools are request/response only — for streaming use a Custom LLM URL instead (separate feature).
Multi-step workflows? Use VAPI Workflows (visual graph) or chain tool calls in your prompt.
Sources
- VAPI Docs - Custom Tools - https://docs.vapi.ai/tools/custom-tools
- VAPI Docs - Default Tools - https://docs.vapi.ai/tools/default-tools
- VAPI Docs - Assistants Quickstart - https://docs.vapi.ai/assistants/quickstart
- ChatBotKit - Build Custom Voice Agents with Vapi - https://chatbotkit.com/tutorials/how-to-build-custom-voice-agents-with-vapi-and-chatbotkit

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.