What is a VAPI‑Twilio TaskRouter Handoff?
A handoff transfers an ongoing voice conversation from an AI assistant (VAPI) to a live human agent using Twilio TaskRouter. The process preserves context, manages state, and handles real‑time interruptions.
- VAPI detects escalation intent via function calling.
- The server creates a TaskRouter task containing the conversation metadata.
- TaskRouter routes the task to an available agent.
- Upon assignment, the server invokes VAPI’s transfer API to bridge the call.
Why Implement a Robust Handoff?
Without careful design, handoffs can fail, causing dropped calls, lost context, or overlapping audio. A reliable implementation delivers:
- Seamless transition with no audible gaps.
- Preserved conversation history for the human agent.
- Graceful fallback when agents are unavailable.
- Scalable handling of rapid user interruptions.
How to Build a Production‑Grade Handoff
The following steps outline a complete, repeatable workflow.
- Prerequisites
- HTTPS‑exposed webhook endpoint (ngrok for local development).
- Twilio account with a TaskRouter workspace.
- VAPI API key with webhook permissions.
- Architecture Overview
- VAPI → Webhook (your server) → TaskRouter task creation.
- TaskRouter → Assignment webhook → Server calls VAPI transfer API.
- Server stores active handoff state in Redis with a TTL (e.g., 30 s).
- Server Setup
- Install dependencies:
npm install express redis axios. - Configure environment variables:
VAPI_SERVER_SECRET, TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_NUMBER, HUMAN_AGENT_NUMBER, SERVER_URL. - Expose the server via ngrok:
ngrok http 3000.
- Install dependencies:
- Webhook Signature Validation
- Compute HMAC‑SHA256 of the raw request body using
VAPI_SERVER_SECRET. - Compare the result to the
Vapi-Signatureheader; reject with 401 on mismatch.
- Compute HMAC‑SHA256 of the raw request body using
- Task Creation
- When VAPI sends a
function-callwebhook withescalate, call Twilio’s TaskRouter API to create a task. - Include
callSid,conversationId, and any captured slots in the task attributes.
- When VAPI sends a
- Agent Assignment Handling
- TaskRouter posts an assignment webhook to your server.
- Store the assignment in Redis:
SET handoff:{callSid} {agentSid} EX 30. - Invoke VAPI’s transfer endpoint with
target=agentPhoneNumberandcallSid.
- Audio Buffer Flush & Debounce
- Immediately flush any pending TTS buffers before calling the transfer API to avoid overlapping speech.
- Implement a 500 ms debounce on consecutive
speech-startedevents to prevent multiple rapid transfers.
- State Lock for Race Conditions
- When a transfer is in progress, set a Redis lock key
lock:{callSid}=1. - If the VAPI end‑of‑call‑report webhook arrives while the lock exists, postpone processing until the lock expires (e.g., 5 s).
- When a transfer is in progress, set a Redis lock key
- Context Preservation
- Persist
call.metadatato a database on everyspeech-startedevent, not only oncall-ended. - When the human agent picks up, retrieve this metadata and display it in the agent UI.
- Persist
- Failure Handling
- If VAPI returns HTTP 409 (call already ended), abort the transfer and send a fallback message via VAPI.
- If no agent answers within 30 s, update the task with
status=failedand route the call back to VAPI with an “all agents busy” prompt.
- Testing the Flow
- Trigger a call from the VAPI dashboard.
- Verify that the greeting webhook fires.
- Speak the escalation keyword (e.g., “speak to a human”) and confirm the handoff.
- Check server logs for successful signature validation and proper state transitions.