---
title: "Build a Voice Agent with VAPI Functions and Custom Tools (2026)"
description: "VAPI deprecated 'Custom Functions' for the Tools API. Wire a webhook tool, secure with HMAC, and ship a working appointment-booker — code + pitfalls."
canonical: https://callsphere.ai/blog/vw9h-build-voice-agent-vapi-functions-tools-2026
category: "AI Voice Agents"
tags: ["VAPI", "Voice Agent", "Custom Tools", "Webhooks", "Functions"]
author: "CallSphere Team"
published: 2026-03-25T00:00:00.000Z
updated: 2026-05-08T03:13:53.241Z
---

# 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

```mermaid
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"]
}
```

## 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](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  --transcript 'I want 3pm tomorrow'` exercises the tool path without spending phone minutes.

## Pitfalls

- **`toolCallId` mismatch**: Your response `results[].toolCallId` MUST match the request `call.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 `200` even 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](https://docs.vapi.ai/tools/custom-tools)
- VAPI Docs - Default Tools - [https://docs.vapi.ai/tools/default-tools](https://docs.vapi.ai/tools/default-tools)
- VAPI Docs - Assistants Quickstart - [https://docs.vapi.ai/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](https://chatbotkit.com/tutorials/how-to-build-custom-voice-agents-with-vapi-and-chatbotkit)

---

Source: https://callsphere.ai/blog/vw9h-build-voice-agent-vapi-functions-tools-2026
