Local webhook testing

Overview

The vapi listen command provides a local webhook forwarding service that receives events and forwards them to your local development server. This helps you debug webhook integrations during development.

Important: vapi listen does NOT provide a public URL or tunnel. You’ll need to use a separate tunneling solution like ngrok to expose your local server to the internet.

In this guide, you’ll learn to:

  • Set up local webhook forwarding with a tunneling service
  • Debug webhook events in real-time
  • Configure advanced forwarding options
  • Handle different webhook types

No automatic tunneling: The vapi listen command is a local forwarder only. It does not create a public URL or tunnel to the internet. You must use a separate tunneling service (like ngrok) and configure your Vapi webhook URLs manually.

Quick start

1

Set up a tunnel to your local machine

Use a tunneling service like ngrok to create a public URL:

$# Example with ngrok
>ngrok http 4242 # 4242 is the default port for vapi listen

Note the public URL provided by your tunneling service (e.g., https://abc123.ngrok.io)

2

Start the webhook listener

$vapi listen --forward-to localhost:3000/webhook

This starts a local server on port 4242 that forwards to your application

3

Update your webhook URLs

Go to your Vapi Dashboard and update your webhook URLs to point to your tunnel URL:

  • Assistant webhook URL: https://abc123.ngrok.io
  • Phone number webhook URL: https://abc123.ngrok.io
  • Or any other webhook configuration
4

Test your webhooks

Trigger webhook events (make calls, etc.) and see them forwarded through the tunnel to your local server

How it works

Current implementation: The vapi listen command acts as a local webhook forwarder only. It receives webhook events on a local port (default 4242) and forwards them to your specified endpoint. To receive events from Vapi, you must:

  1. Use a tunneling service (ngrok, localtunnel, etc.) to expose port 4242 to the internet
  2. Configure your Vapi webhook URLs to point to the tunnel URL
  3. The flow is: Vapi → Your tunnel URL → vapi listen (port 4242) → Your local server
1

Local forwarder starts

The CLI starts a webhook forwarder on port 4242 (configurable)

2

Tunnel exposes the forwarder

Your tunneling service creates a public URL that routes to port 4242

3

Configure webhook URL

Update your Vapi webhook URL to point to the tunnel’s public URL

4

Events forwarded

Webhook events flow: Vapi → Tunnel → CLI forwarder → Your local endpoint

5

Real-time logging

Events are displayed in your terminal for debugging

Basic usage

Standard forwarding

Forward to your local development server:

$# Forward to localhost:3000/webhook
>vapi listen --forward-to localhost:3000/webhook
>
># Short form
>vapi listen -f localhost:3000/webhook

Custom port

Use a different port for the webhook listener:

$# Listen on port 8080 instead of default 4242
>vapi listen --forward-to localhost:3000/webhook --port 8080
>
># Remember to update your tunnel to use port 8080
>ngrok http 8080

Skip TLS verification

For development with self-signed certificates:

$vapi listen --forward-to https://localhost:3000/webhook --skip-verify

Only use --skip-verify in development. Never in production.

Understanding the output

When you run vapi listen, you’ll see:

$$ vapi listen --forward-to localhost:3000/webhook
>
>🎧 Vapi Webhook Listener
>📡 Listening on: http://localhost:4242
>📍 Forwarding to: http://localhost:3000/webhook
>
>⚠️ To receive Vapi webhooks:
> 1. Use a tunneling service (e.g., ngrok http 4242)
> 2. Update your Vapi webhook URLs to the tunnel URL
>
>Waiting for webhook events...
>
>[2024-01-15 10:30:45] POST /
>Event: call-started
>Call ID: call_abc123def456
>Status: 200 OK (45ms)
>
>[2024-01-15 10:30:52] POST /
>Event: speech-update
>Transcript: "Hello, how can I help you?"
>Status: 200 OK (12ms)

Webhook event types

The listener forwards all Vapi webhook events:

  • call-started - Call initiated
  • call-ended - Call completed
  • call-failed - Call encountered an error
  • speech-update - Real-time transcription
  • transcript - Final transcription
  • voice-input - User speaking detected
  • function-call - Tool/function invoked
  • assistant-message - Assistant response
  • conversation-update - Conversation state change
  • error - Error occurred
  • recording-ready - Call recording available
  • analysis-ready - Call analysis complete

Advanced configuration

Headers and authentication

The listener adds helpful headers to forwarded requests:

1X-Forwarded-For: vapi-webhook-listener
2X-Original-Host: <your-tunnel-domain>
3X-Webhook-Event: call-started
4X-Webhook-Timestamp: 1705331445

Your server receives the exact webhook payload from Vapi with these additional headers for debugging.

Setting up with different tunneling services

$# Terminal 1: Start ngrok tunnel
>ngrok http 4242
>
># Terminal 2: Start vapi listener
>vapi listen --forward-to localhost:3000/webhook
>
># Use the ngrok URL in Vapi Dashboard
$# Terminal 1: Install and start localtunnel
>npm install -g localtunnel
>lt --port 4242
>
># Terminal 2: Start vapi listener
>vapi listen --forward-to localhost:3000/webhook
>
># Use the localtunnel URL in Vapi Dashboard
$# Terminal 1: Start cloudflare tunnel
>cloudflared tunnel --url http://localhost:4242
>
># Terminal 2: Start vapi listener
>vapi listen --forward-to localhost:3000/webhook
>
># Use the cloudflare URL in Vapi Dashboard

Pro tip: Some tunneling services offer static URLs (like ngrok with a paid plan), which means you won’t need to update your Vapi webhook configuration every time you restart development.

Filtering events

Filter specific event types (coming soon):

$# Only forward call events
>vapi listen --forward-to localhost:3000 --filter "call-*"
>
># Multiple filters
>vapi listen --forward-to localhost:3000 --filter "call-started,call-ended"

Response handling

The listener expects standard HTTP responses:

  • 200-299: Success, event processed
  • 400-499: Client error, event rejected
  • 500-599: Server error, will retry

Development workflow

Typical setup

1

Start your local server

$# In terminal 1
>npm run dev # Your app on localhost:3000
2

Start tunnel to expose port 4242

$# In terminal 2
>ngrok http 4242 # Creates public URL for the CLI listener
># Note the public URL (e.g., https://abc123.ngrok.io)
3

Start webhook listener

$# In terminal 3
>vapi listen --forward-to localhost:3000/api/vapi/webhook
4

Configure Vapi webhooks

Update your Vapi webhook URLs to point to the ngrok URL from step 2

5

Make test calls

Use the Vapi dashboard or API to trigger webhooks

6

Debug in real-time

See events in the CLI terminal and debug your handler

Data flow: Vapi sends webhooks → Ngrok tunnel (public URL) → vapi listen (port 4242) → Your local server (port 3000)

Example webhook handler

1app.post('/api/vapi/webhook', async (req, res) => {
2 const { type, call, timestamp } = req.body;
3
4 console.log(`Webhook received: ${type} at ${timestamp}`);
5
6 switch (type) {
7 case 'call-started':
8 console.log(`Call ${call.id} started with ${call.customer.number}`);
9 break;
10
11 case 'speech-update':
12 console.log(`User said: ${req.body.transcript}`);
13 break;
14
15 case 'function-call':
16 const { functionName, parameters } = req.body.functionCall;
17 console.log(`Function called: ${functionName}`, parameters);
18
19 // Return function result
20 const result = await processFunction(functionName, parameters);
21 return res.json({ result });
22
23 case 'call-ended':
24 console.log(`Call ended. Duration: ${call.duration}s`);
25 break;
26 }
27
28 res.status(200).send();
29});

Testing scenarios

Simulating errors

Test error handling in your webhook:

$# Your handler returns 500
>vapi listen --forward-to localhost:3000/webhook-error
>
># Output shows:
># Status: 500 Internal Server Error (23ms)
># Response: {"error": "Database connection failed"}

Load testing

Test with multiple concurrent calls:

$# Terminal 1: Start listener
>vapi listen --forward-to localhost:3000/webhook
>
># Terminal 2: Trigger multiple calls via API
>for i in {1..10}; do
> vapi call create --to "+1234567890" &
>done

Debugging specific calls

Filter logs by call ID:

$# Coming soon
>vapi listen --forward-to localhost:3000 --call-id call_abc123

Security considerations

The vapi listen command is designed for development only. In production, use proper webhook endpoints with authentication.

Best practices

  1. Never expose sensitive data in console logs
  2. Validate webhook signatures in production
  3. Use HTTPS for production endpoints
  4. Implement proper error handling
  5. Set up monitoring for production webhooks

Production webhook setup

For production, configure webhooks in the Vapi dashboard:

1// Production webhook with signature verification
2app.post('/webhook', verifyVapiSignature, async (req, res) => {
3 // Your production handler
4});

Troubleshooting

If you see “connection refused”:

  1. Verify your server is running on the specified port
  2. Check the endpoint path matches your route
  3. Ensure no firewall is blocking local connections
$# Test your endpoint directly
>curl -X POST http://localhost:3000/webhook -d '{}'

For timeout issues:

  1. Check response time - Vapi expects < 10s response
  2. Avoid blocking operations in webhook handlers
  3. Use async processing for heavy operations
1// Good: Quick response
2app.post('/webhook', async (req, res) => {
3 // Queue for processing
4 await queue.add('process-webhook', req.body);
5 res.status(200).send();
6});

If events aren’t appearing:

  1. Check CLI authentication - vapi auth whoami
  2. Verify account access to the resources
  3. Ensure events are enabled in assistant config
$# Re-authenticate if needed
>vapi login

For HTTPS endpoints:

$# Development only - skip certificate verification
>vapi listen --forward-to https://localhost:3000 --skip-verify
>
># Or use HTTP for local development
>vapi listen --forward-to http://localhost:3000

Next steps

Now that you can test webhooks locally:


Pro tip: Keep vapi listen running while developing - you’ll see all events in real-time and can iterate quickly on your webhook handlers without deployment delays!