Build a Voice Agent with VAPI Functions and Custom Tools (2026)
VAPI deprecated 'Custom Functions' for the Tools API. Wire a webhook tool, secure with HMAC, and ship a working appointment-booker — code + pitfalls.
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
CallSphere ships 37 agents · 90+ tools · 115+ DB tables · 6 verticals, including a VAPI tier for low-volume franchise customers. $149/$499/$1,499 · 14-day trial · 22% affiliate.
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
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.