You can spin up a fully hands-free outbound dialer that greets prospects, answers objections, and writes the outcome straight into your CRM - all without hiring additional SDRs. In this guide I'll show you how to connect Vapi's voice-AI gateway to OpenAI's conversational model, enrich the call with ElevenLabs TTS, and push results through a webhook to a CRM of your choice. By the end you'll have a production-ready pipeline that can run 50-100 calls per hour and feed qualified leads directly into your funnel.
Voice AI agent is a software service that receives a phone call, turns the caller's speech into text, runs that text through a language model, and replies with synthesized speech, all in real time.
What you need
| Tool | Plan / Price* | Role |
|---|---|---|
| Docker Engine (Community Edition) | Free (self-hosted) | Container runtime for the whole stack |
| Vapi | Pay-as-you-go (check Vapi's current pricing) | Inbound/outbound telephony, SIP bridge, call event webhook |
| OpenAI API | Pay-as-you-go (check OpenAI's current pricing) | Generates dynamic dialog and lead-qualification logic |
| ElevenLabs TTS | Pay-as-you-go (check ElevenLabs pricing) | High-quality voice synthesis for the agent |
| Twilio Programmable Voice | Pay-as-you-go (check Twilio's current pricing) | Provides a public phone number that forwards to Vapi |
| n8n (open-source workflow engine) | Free (self-hosted) | Orchestrates webhook handling, API calls, and CRM updates |
| PostgreSQL (or any SQL DB) | Free (self-hosted) | Persists call transcripts and scoring metadata |
| Your CRM (HubSpot, Pipedrive, Salesforce, etc.) | Whatever plan you already have | Stores qualified lead records |
| ngrok (optional for local dev) | Free tier available (check ngrok's current pricing) | Exposes local webhook endpoints during development |
\*Pricing notes are deliberately vague; always verify the latest rates on the provider's website before committing budget.
Rough build time: 8-12 hours for a developer comfortable with Docker and REST APIs.
Build it
1. Spin up the infrastructure
Create a Docker-Compose file that launches n8n, PostgreSQL, and a tiny Reverse Proxy (Caddy) to expose the webhook endpoint securely.
This file creates three containers: a PostgreSQL instance for persisting call data, n8n as the orchestrator, and Caddy to terminate TLS for the public webhook URL.
Run docker compose up -d. Verify each service is reachable (docker ps should list three containers).
2. Register a Twilio phone number
Log into the Twilio console, buy a local number, and point its Voice → Webhooks URL to the Caddy-exposed endpoint, e.g.:
Set the request method to POST and enable Webhook for both Incoming Calls and Call Status.
3. Connect Twilio ↔ Vapi
Create a Vapi Outbound Flow that uses the Twilio number as the source. In the Vapi dashboard go to Flows → New Flow → Outbound and set:
- Source Number: the Twilio number you just bought
- Destination: dynamic; will be supplied by n8n when the workflow triggers
- Callback URL:
https://yourdomain.com/webhook/vapi(the same endpoint you configured in Twilio)
Vapi will POST a JSON payload for each call event (ringing, answered, speech, etc.) to that URL.
4. Design the n8n workflow
- Webhook Node - listens on
/webhook/vapi. Set Response Mode to "Immediately respond 200 OK" so Twilio/Vapi don't time out. - IF Node - checks
event.typefield. Ifevent.type === "answer"proceed; otherwise ignore. - HTTP Request Node (Speech-to-Text) - call Vapi's
/speech-to-textendpoint, passingevent.recording_url. Store result in{{ $json["transcript"] }}. - OpenAI Node - prompt the latest transcript with a system prompt that encodes your sales script. Example prompt (see code block below).
- ElevenLabs TTS Node - feed OpenAI's reply text, receive an audio URL, and return it to Vapi via a HTTP Request
PUT /call/{callId}/audio. - PostgreSQL Node - INSERT call metadata (caller, transcript, AI reply, confidence score) into
callstable. - CRM HTTP Node - POST qualified lead JSON to your CRM's REST endpoint (HubSpot contacts, for example).
#### OpenAI Prompt (Step 4)
The JSON is sent directly to
https://api.openai.com/v1/chat/completions. The response'schoices[0].message.contentbecomes the text that ElevenLabs will speak back to the prospect.
5. Deploy the webhook in Caddy
Create a Caddyfile that terminates TLS with Let's Encrypt and forwards /webhook/vapi to the n8n container:
Reload Caddy (docker exec caddy caddy reload). Verify the endpoint works with a simple curl -X POST https://yourdomain.com/webhook/vapi -d '{}'. You should receive 200 OK.
6. Test end-to-end
- Use Twilio's Call Simulator (or dial the number from a real phone).
- When the call connects, Vapi streams inbound audio to the webhook.
- n8n converts speech → text → OpenAI → ElevenLabs → Vapi, completing a round-trip conversation.
- After the call, check PostgreSQL (
SELECT * FROM calls ORDER BY created_at DESC LIMIT 1;) and your CRM dashboard for a new lead entry.
If everything logs correctly, you have a functional automated cold calling for b2b leads pipeline.
7. Add lead scoring
Create a second IF Node after the OpenAI response that looks for keywords like "interested", "demo", "budget". Set a numeric score (e.g., 80) when those appear; otherwise assign a lower score (20). Store the score in the PostgreSQL row and send it to the CRM as a custom field. This enables your sales team to prioritize follow-ups automatically.
Where this breaks
| Failure mode | Symptom | Fix |
|---|---|---|
| Vapi webhook timeout | Twilio logs "504 Gateway Timeout". | Ensure the n8n webhook node is set to Immediately respond 200 OK; handle heavy processing in downstream nodes, not the initial response. |
| Speech-to-text latency | Prospect hears awkward pauses >3 s. | Cache the OpenAI response locally for identical utterances, or pre-record short greetings with ElevenLabs and play them while waiting for the AI reply. |
| OpenAI rate limits | 429 "Too Many Requests" errors in n8n logs. | Implement an exponential back-off in the OpenAI HTTP node; keep calls under the provider's per-minute limit (check OpenAI's current docs). |
| Twilio cost surprise | Unexpected per-minute charges appear on the bill. | Monitor Twilio usage daily; set alerts in the Twilio console for spend > $20. |
| CRM field mismatch | Leads appear with empty "Company" field. | Verify the JSON payload matches the CRM's required schema; use the CRM's Test API endpoint to inspect field names. |
| Docker container crashes | docker ps shows n8n exiting with code 1. | Check docker logs n8n; most crashes stem from missing env vars (POSTGRES_PASSWORD). Add missing vars to the compose file and restart. |
| TLS certificate renewal failure | HTTPS requests return 502 after 90 days. | Caddy auto-renews, but only if port 80 is reachable. Ensure firewall rules allow inbound traffic on 80/443. |
Key warning: The whole chain is only as reliable as its slowest link. In practice, the OpenAI request adds ~500 ms of latency; budget extra buffer in your call flow to avoid breaking the natural conversation rhythm.
For a deeper technical reference, see n8n's documentation.