Skip to content

dav12072018/vapi-clawbot-integration

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vapi + OpenClaw Integration

Complete setup guide for integrating Vapi voice calling with OpenClaw, including the "ask human" workflow where the AI can request information via Telegram during a call.

Overview

This integration enables your OpenClaw agent to:

  • Make and receive phone calls via Vapi
  • Ask you for information during calls via Telegram when it doesn't know something
  • Relay your responses back to the caller seamlessly
  • Handle complex phone tasks (restaurant reservations, customer service, etc.)

Prerequisites

  • OpenClaw instance with Telegram configured
  • Vapi account (vapi.ai)
  • A domain with SSL (for webhooks)
  • Node.js (for the webhook proxy server)

Architecture

Phone Call ↔ Vapi ↔ Assistant ↔ ask_human tool ↔ Webhook Proxy ↔ Telegram ↔ You

When the AI doesn't know something (your insurance number, preferences, etc.), it:

  1. Says "one sec, let me check on that" to the caller
  2. Calls the ask_human tool with the question
  3. Webhook proxy sends you a Telegram message
  4. Polls for your reply (up to 2 minutes)
  5. Relays your answer back to the caller

Step 1: Create Vapi Assistant

1.1 Generate Webhook Secret

First, create a strong webhook secret for security:

# Generate a secure random secret
openssl rand -hex 32
# Save this - you'll need it for VAPI_WEBHOOK_SECRET and Vapi configuration

1.2 Create Assistant

curl -X POST "https://api.vapi.ai/assistant" \
  -H "Authorization: Bearer YOUR_VAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "OpenClaw Voice Assistant",
    "model": {
      "provider": "openai",
      "model": "gpt-4o-mini",
      "temperature": 0.7
    },
    "voice": {
      "provider": "vapi",
      "voiceId": "elliot"
    },
    "transcriber": {
      "provider": "deepgram",
      "model": "nova-3"
    },
    "firstMessage": "Hi, this is your AI assistant calling. How can I help you today?",
    "systemPrompt": "You are a helpful voice assistant on a phone call. Keep responses brief and conversational (1-2 sentences max). Be natural and friendly.\n\nIMPORTANT RULE: If you are asked something you don'\''t know (personal details, insurance info, preferences, etc.), say something natural like '\''one sec, let me check on that'\'' or '\''hold on, let me get that info for you.'\'' Then use the ask_human tool to get the answer. Wait for the response and relay it back. NEVER guess or make something up — always ask when you don'\''t know.\n\nPhone numbers:\n- Primary: YOUR_PRIMARY_PHONE\n- Backup: YOUR_BACKUP_PHONE",
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "ask_human",
          "description": "Ask the human for information you don'\''t have. Use when you need personal details, preferences, or any info not in your knowledge.",
          "parameters": {
            "type": "object",
            "properties": {
              "question": {
                "type": "string",
                "description": "The question to ask the human"
              },
              "context": {
                "type": "string",
                "description": "Brief context about why you need this info"
              }
            },
            "required": ["question"]
          }
        }
      }
    ]
  }'

Save the returned assistantId.

1.3 Get Phone Number

# Import your existing number (if you have one with Twilio/etc)
curl -X POST "https://api.vapi.ai/phone-number" \
  -H "Authorization: Bearer YOUR_VAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "twilio",
    "twilioAccountSid": "YOUR_TWILIO_SID",
    "twilioAuthToken": "YOUR_TWILIO_TOKEN",
    "number": "+1YOURNUMBER"
  }'

# OR get a new Vapi number
curl -X POST "https://api.vapi.ai/phone-number" \
  -H "Authorization: Bearer YOUR_VAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "vapi",
    "areaCode": "415"
  }'

Save the returned phoneNumberId.

Step 2: Webhook Proxy Server

Create the webhook proxy that handles the ask_human tool calls.

2.1 Create proxy.mjs

import express from 'express';
import fetch from 'node-fetch';

const app = express();
app.use(express.json());

const CONFIG = {
  VAPI_API_KEY: process.env.VAPI_API_KEY,
  TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN,
  TELEGRAM_CHAT_ID: process.env.TELEGRAM_CHAT_ID, // Your Telegram chat ID
  PORT: process.env.PORT || 3334
};

// Store pending questions
const pendingQuestions = new Map();

// Webhook endpoint for Vapi tool calls
app.post('/vapi', async (req, res) => {
  try {
    const { call, tool } = req.body;
    
    if (tool?.function?.name === 'ask_human') {
      const { question, context } = tool.function.arguments;
      const questionId = `q_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
      
      console.log(`[${questionId}] Received question: ${question}`);
      
      // Send question to Telegram
      const telegramText = `📞 Question from call:\\n\\n**${question}**\\n\\n${context ? `Context: ${context}\\n\\n` : ''}Reply to this message to answer.`;
      
      const telegramResponse = await fetch(`https://api.telegram.org/bot${CONFIG.TELEGRAM_BOT_TOKEN}/sendMessage`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          chat_id: CONFIG.TELEGRAM_CHAT_ID,
          text: telegramText,
          parse_mode: 'Markdown'
        })
      });
      
      const telegramResult = await telegramResponse.json();
      if (!telegramResult.ok) {
        console.error('Failed to send Telegram message:', telegramResult);
        return res.json({ result: "Sorry, I couldn't get that information right now." });
      }
      
      const messageId = telegramResult.result.message_id;
      
      // Store the question and start polling
      pendingQuestions.set(questionId, {
        telegramMessageId: messageId,
        question,
        timestamp: Date.now()
      });
      
      // Poll for response
      const answer = await pollForAnswer(questionId, messageId);
      pendingQuestions.delete(questionId);
      
      return res.json({ result: answer });
    }
    
    res.json({ result: "Tool not recognized" });
    
  } catch (error) {
    console.error('Webhook error:', error);
    res.json({ result: "Sorry, there was an error getting that information." });
  }
});

async function pollForAnswer(questionId, telegramMessageId) {
  const maxWaitTime = 120000; // 2 minutes
  const pollInterval = 2000; // 2 seconds
  const startTime = Date.now();
  
  while (Date.now() - startTime < maxWaitTime) {
    try {
      // Check for replies to our message
      const updatesResponse = await fetch(`https://api.telegram.org/bot${CONFIG.TELEGRAM_BOT_TOKEN}/getUpdates?offset=-10&limit=10`);
      const updates = await updatesResponse.json();
      
      if (updates.ok && updates.result) {
        for (const update of updates.result) {
          const message = update.message;
          if (message?.reply_to_message?.message_id === telegramMessageId) {
            console.log(`[${questionId}] Got answer: ${message.text}`);
            return message.text;
          }
        }
      }
      
      await new Promise(resolve => setTimeout(resolve, pollInterval));
    } catch (error) {
      console.error('Polling error:', error);
    }
  }
  
  console.log(`[${questionId}] Timeout waiting for answer`);
  return "I didn't get a response in time. Let me continue without that information.";
}

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: Date.now() });
});

app.listen(CONFIG.PORT, () => {
  console.log(`Vapi webhook proxy listening on port ${CONFIG.PORT}`);
});

2.2 Package.json

{
  "name": "vapi-webhook-proxy",
  "version": "1.0.0",
  "type": "module",
  "dependencies": {
    "express": "^4.18.0",
    "node-fetch": "^3.3.0"
  },
  "scripts": {
    "start": "node proxy.mjs"
  }
}

2.3 Environment Variables

Create .env file:

VAPI_API_KEY=your_vapi_api_key_here
VAPI_WEBHOOK_SECRET=your_32_char_hex_secret_here
TELEGRAM_BOT_TOKEN=your_telegram_bot_token
TELEGRAM_CHAT_ID=your_telegram_chat_id
PORT=3334
DEBUG=false

⚠️ Security: The VAPI_WEBHOOK_SECRET is critical - it prevents unauthorized access to your webhook.

Step 3: Expose Webhook

Option A: Cloudflare Tunnel (Recommended)

# Install cloudflared
# macOS: brew install cloudflare/cloudflare/cloudflared
# Linux: Follow https://pkg.cloudflare.com/

# Login and create tunnel
cloudflared tunnel login
cloudflared tunnel create vapi-webhook

# Start proxy server
npm install && npm start &

# Expose via tunnel
cloudflared tunnel --url http://localhost:3334
# Note the public URL (e.g., https://xxx.trycloudflare.com)

Option B: ngrok

npm install && npm start &
ngrok http 3334
# Note the public URL

Step 4: Configure Vapi Assistant Tools

Update your assistant with the webhook URL:

curl -X PATCH "https://api.vapi.ai/assistant/YOUR_ASSISTANT_ID" \
  -H "Authorization: Bearer YOUR_VAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "ask_human",
          "description": "Ask the human for information you don'\''t have",
          "parameters": {
            "type": "object",
            "properties": {
              "question": {
                "type": "string",
                "description": "The question to ask"
              },
              "context": {
                "type": "string", 
                "description": "Why you need this info"
              }
            },
            "required": ["question"]
          }
        },
        "server": {
          "url": "https://YOUR_WEBHOOK_URL/vapi",
          "secret": "your_32_char_hex_secret_here"
        }
      }
    ]
  }'

Step 5: Test the Integration

5.1 Test Outbound Call

curl -X POST "https://api.vapi.ai/call" \
  -H "Authorization: Bearer YOUR_VAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "assistantId": "YOUR_ASSISTANT_ID",
    "phoneNumberId": "YOUR_PHONE_NUMBER_ID", 
    "customer": { "number": "+1YOUR_TEST_NUMBER" }
  }'

5.2 Test ask_human Flow

  1. Make a call to your test number
  2. When connected, ask the AI something it doesn't know: "What's my insurance number?"
  3. AI should say "let me check on that"
  4. You should receive a Telegram message
  5. Reply to the Telegram message
  6. AI should relay your answer back on the call

Step 6: OpenClaw Integration (Optional)

If you want to trigger calls from OpenClaw:

6.1 Add Vapi Skill

Create skills/vapi/SKILL.md:

---
name: vapi
description: Make voice calls via Vapi for restaurant reservations, customer service, etc.
---

# Vapi Voice Calling

Use Vapi to make outbound voice calls when the user needs to call businesses, make reservations, or handle tasks requiring human conversation.

## Usage

Always confirm with the user before making calls. Include the purpose and what you plan to say.

6.2 Create Vapi Script

Create skills/vapi/vapi.mjs:

#!/usr/bin/env node
import fetch from 'node-fetch';

const VAPI_API_KEY = process.env.VAPI_API_KEY;
const ASSISTANT_ID = "YOUR_ASSISTANT_ID";
const PHONE_NUMBER_ID = "YOUR_PHONE_NUMBER_ID";

async function makeCall(targetNumber, purpose) {
  const response = await fetch('https://api.vapi.ai/call', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${VAPI_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      assistantId: ASSISTANT_ID,
      phoneNumberId: PHONE_NUMBER_ID,
      customer: { number: targetNumber },
      assistantOverrides: {
        firstMessage: `Hi, this is calling about ${purpose}. How can I help you today?`
      }
    })
  });
  
  const result = await response.json();
  console.log(JSON.stringify(result, null, 2));
}

const [targetNumber, ...purposeParts] = process.argv.slice(2);
const purpose = purposeParts.join(' ');

if (!targetNumber || !purpose) {
  console.error('Usage: node vapi.mjs <phone-number> <purpose>');
  process.exit(1);
}

makeCall(targetNumber, purpose);

Security Considerations

Critical Security Fixes Applied

  1. Webhook Authentication: All requests must include valid HMAC-SHA256 signature using VAPI_WEBHOOK_SECRET
  2. Sensitive Data Logging: Questions and answers are not logged in production (only with DEBUG=true)
  3. Environment Variables: All secrets stored in environment variables, never committed to git

Additional Security Recommendations

  1. Use Private Telegram Chat: Never use group chats for sensitive responses
  2. Monitor Webhook Logs: Watch for unauthorized access attempts
  3. Rotate Secrets: Periodically change VAPI_WEBHOOK_SECRET
  4. Consider IP Allowlisting: Restrict webhook access to Vapi's IP ranges if possible
  5. Use HTTPS: Always expose webhooks over HTTPS (Cloudflare Tunnel/ngrok provide this)

Troubleshooting

Common Issues

  1. Webhook returning 401 Unauthorized: Check VAPI_WEBHOOK_SECRET matches in both .env and Vapi assistant configuration
  2. Webhook not receiving calls: Verify webhook URL is publicly accessible and uses HTTPS
  3. Telegram messages not sending: Verify bot token and chat ID are correct
  4. Call quality issues: Check Vapi voice settings and internet connection
  5. Tool calls failing silently: Enable DEBUG=true to see detailed logs

Debugging

Enable verbose logging:

// Add to proxy.mjs
const DEBUG = process.env.DEBUG === 'true';
if (DEBUG) console.log('Request body:', JSON.stringify(req.body, null, 2));

Logs

  • Vapi calls: Dashboard at vapi.ai
  • Webhook: Check your server logs
  • Telegram: Bot API responses

Cost Optimization

  • Use GPT-4o-mini instead of GPT-4 for cost savings
  • Set call duration limits
  • Implement usage monitoring

Next Steps

  1. Set up monitoring and alerts
  2. Add call recording/transcription
  3. Integrate with CRM or ticketing system
  4. Add support for conference calls
  5. Implement voicemail detection and handling

Support

About

Vapi + OpenClaw integration: AI voice calls with ask-human-via-Telegram workflow

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors