Voice setup

Vapi setup, the Form builder, mid-call tools, the Test page, and the Logs page.

Voice workspaces dispatch outbound phone calls via Vapi. Coachbot owns the form, the lead database, the calendar booking flow, and the Logs page; Vapi handles the assistant's voice, LLM choice, first message, and the actual conversation.

1. Connect Vapi

Settings → Integrations → VapiConnect:

  • API key — Vapi dashboard → API Keys → Private.
  • Assistant ID — Vapi → Assistants → your assistant → top of page.
  • Phone number ID — Vapi → Phone Numbers → your number → ID.

On save, Coachbot generates a tool bearer token and displays seven URLs for the assistant's tools + events webhook. Copy all of them.

Hit Verify. The check pings Vapi's /assistant/<id> and /phone-number/<id> to confirm both IDs resolve.

2. Wire the tools in Vapi

Vapi → Assistants → <your assistant> → Tools section. Six tools, each as a custom function (not a saved tool by reference — those break if anyone deletes the source). For every tool, set:

  • Server URL — the matching URL from Coachbot's Vapi card.
  • HeadersAuthorization = Bearer <your tool secret>.

The tools:

Tool nameWhen the assistant calls itParameters
updateLeadProfileAfter learning a qualification answergoal, fitnessLevel (enum), budget, schedulePref, notes (all optional)
listSlotsWhen ready to offer timesdaysAhead (optional, default 7)
bookConsultWhen the lead picks a slotstart (required, ISO string from listSlots), name, email, phone, notes
markUnqualifiedWhen a disqualifier appliesreason (required)
escalateToHumanWhen out of scope or a human is requestedreason (required)
endConversationTo end the call cleanlynone

The exact parameter JSON schemas are in app/api/vapi/[slug]/tools/[tool]/route.ts.

3. Wire the assistant's Server URL (end-of-call events)

Vapi → <your assistant> → Server / Webhooks:

  • Server URLhttps://<your-host>/api/vapi/<slug>/events
  • Authorization header — same Bearer <tool secret>.

This receives status-update (status changes) and end-of-call-report (final transcript + summary + recording URL).

4. Paste the prompt template

/admin/<slug>/agent shows a sky banner with Copy Vapi prompt template. Click → paste into your Vapi assistant's System Prompt field → save the assistant.

After this, every Coachbot edit on the Agent page takes effect on the next call — no Vapi changes needed. See Agent for the variable map.

5. First Message

Vapi has a separate First Message field (what the assistant says when the call connects). Either leave it blank (the LLM improvises from your system prompt's greeting instruction) or set it to:

Hi {{leadFirstName}}, this is {{agentName}} from {{businessName}}.
You just submitted our form — got a minute to chat?

The static template is more consistent — the LLM-improvised greeting can occasionally drift.

6. Configure the Form (voice-only feature)

Voice workspaces have a Form builder at /admin/<slug>/form. This defines the questions leads answer before the call happens.

  • Step 1: Contact info — locked. Every lead enters name + email + phone + consent.
  • Steps 2…N: Qualifier questions — operator-configured. Each has:
    • Title — short label.
    • TypeYes / No or Short text.
    • Required — whether the lead must answer to advance.

Up to 20 questions. Stored as JSON on the workspace settings, with stable IDs so reordering doesn't orphan past responses.

On submit, the answers land on lead.form_responses. Coachbot builds a summary string and passes it to Vapi as {{qualificationContext}} so the assistant opens the call already knowing what the lead answered.

Testing

Two ways to verify before going live:

/admin/<slug>/test — Test call page (voice-only)

A button-driven form: name + phone + email + optional qualification context. Click Dial now and Coachbot calls Vapi's /call API synchronously — you see the result inline:

  • ✅ Vapi call ID + a link to the lead's transcript page + a link to the Logs page. Your phone should ring within ~10 seconds.
  • ❌ The exact Vapi error string. No more "submitted the form, nothing happened" mystery.

The test creates a [Test]-prefixed lead row + a real call_session, exercises the full pipeline including tool callbacks. Cleanup after testing by deleting the lead from /admin/<slug>/leads.

/forms/<slug> — the real public form

Fill it with a real phone. Same code path as the API and the Test page (they all call dispatchVoiceCall).

Logs page

/admin/<slug>/logs (voice-only) renders the last 200 rows of webhook_logs — every Vapi-related request that crossed Coachbot's boundary.

ColumnMeans
Direction arrow outbound to Vapi, inbound from Vapi
Sourcevapi-dispatch (form-triggered call), vapi-test (Test page), vapi-tool (mid-call tool callback), vapi-event (status update or end-of-call)
PathURL Vapi hit, or https://api.vapi.ai/call outbound
Status2xx green / 4xx amber / 5xx red
LeadJoined from leads.name when known
DurationPer-request timing

Click any row to expand request + response JSON. Errors render in a red box.

End-of-call: transcript + recording

When the call ends, Vapi fires two events to your events URL:

  1. status-update with status: "ended" — immediately. Coachbot builds a transcript from artifact.messages and saves it to call_session.transcript. The transcript shows on the lead detail page within seconds.
  2. end-of-call-report — usually 10–60 seconds later. Coachbot upgrades the transcript with Vapi's cleaner version and adds the summary, recordingUrl, durationSeconds.

Worst case (end-of-call-report never arrives), you still have the transcript from the status-update event.