---
created: 2026-03-12
source: Rivet
tags: [agent-archive, rivet]
---

# VAPI Webhook Tools Specification for Phone-Rivet

> **Updated:** 2026-02-06 | **Status:** Ready for Implementation

## Overview

Enable phone-Rivet (VAPI voice assistant) to take actions during calls:
- Create/update TODOs
- Schedule reminders  
- Look up Growth Engine leads
- Send messages to Michael

## How VAPI Function Calling Works

### Tool Types Available

1. **Custom Tools (Function)** - Your own webhooks ✅ *We'll use this*
2. **Code Tools** - TypeScript on VAPI infrastructure
3. **Integration Tools** - Pre-built (Make, GHL, etc.)

### Request Format (What VAPI Sends)

When phone-Rivet calls a tool, your webhook receives:

```json
{
  "message": {
    "type": "tool-calls",
    "timestamp": 1707220914567,
    "toolCallList": [
      {
        "id": "toolu_01DTPAzUm5Gk3zxrpJ969oMF",
        "name": "create_todo",
        "arguments": {
          "title": "Follow up with John about quote",
          "priority": "high"
        }
      }
    ],
    "call": {
      "id": "call-uuid-here",
      "orgId": "org-uuid",
      "type": "inboundPhoneCall",
      "customer": {
        "number": "+61400123456"
      }
    },
    "assistant": {
      "name": "phone-Rivet"
    }
  }
}
```

### Response Format (What You Return)

```json
{
  "results": [
    {
      "toolCallId": "toolu_01DTPAzUm5Gk3zxrpJ969oMF",
      "result": "TODO created: Follow up with John about quote (ID: todo_123)"
    }
  ]
}
```

**Key insight:** The `result` is what gets spoken back to the caller, so make it conversational.

---

## Architecture

```
┌─────────────────┐     ┌──────────────────────┐     ┌─────────────────┐
│  VAPI Phone     │     │  Webhook Server      │     │  Integrations   │
│  Assistant      │────►│  (Railway/Express)   │────►│                 │
│  +61 238 205 443│     │                      │     │  • Google Tasks │
└─────────────────┘     │  POST /vapi/tools/*  │     │  • Google Cal   │
                        │                      │     │  • Growth Engine│
                        │  Auth: Bearer Token  │     │  • Telegram Bot │
                        └──────────────────────┘     └─────────────────┘
```

### Where to Host

**Option A: Add to Growth Engine** (Recommended)
- Already on Railway, already has API
- Add `/api/vapi/*` routes
- Reuse existing Google/Supabase connections

**Option B: Standalone Service**
- Separate deployment
- More isolation but more moving parts

---

## Authentication

### VAPI Custom Credentials (Recommended)

In VAPI Dashboard → Custom Credentials → Create:

```
Type: Bearer Token
Name: "RateRight Webhook Auth"
Header: Authorization
Token: <generate-secure-token>
Include Bearer Prefix: Yes
```

Then reference in tool config:
```json
{
  "server": {
    "url": "https://rateright-growth-production.up.railway.app/api/vapi/tools",
    "credentialId": "cred_abc123"
  }
}
```

### Server-Side Validation

```javascript
// Simple Bearer token validation
const validateAuth = (req, res, next) => {
  const auth = req.headers.authorization;
  const expected = `Bearer ${process.env.VAPI_WEBHOOK_TOKEN}`;
  
  if (auth !== expected) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
};
```

---

## Tools to Implement

### 1. create_todo

**Purpose:** Create a TODO/task during the call

**VAPI Tool Definition:**
```json
{
  "type": "function",
  "function": {
    "name": "create_todo",
    "description": "Create a TODO item for follow-up. Use when the caller mentions something that needs to be done later.",
    "parameters": {
      "type": "object",
      "properties": {
        "title": {
          "type": "string",
          "description": "What needs to be done"
        },
        "priority": {
          "type": "string",
          "enum": ["low", "medium", "high"],
          "description": "How urgent is this"
        },
        "notes": {
          "type": "string",
          "description": "Additional context from the call"
        }
      },
      "required": ["title"]
    }
  },
  "server": {
    "url": "https://rateright-growth-production.up.railway.app/api/vapi/tools",
    "credentialId": "cred_rateright_webhook"
  },
  "messages": {
    "requestStarted": "Let me create that task for you...",
    "requestFailed": "I couldn't create that task right now, but I'll remember to tell Michael."
  }
}
```

**Webhook Handler:**
```javascript
async function handleCreateTodo(toolCall, callContext) {
  const { title, priority = 'medium', notes } = toolCall.arguments;
  const callerNumber = callContext.customer?.number || 'Unknown';
  
  // Option 1: Google Tasks API
  const task = await googleTasks.insert({
    tasklist: '@default',
    resource: {
      title: title,
      notes: `Priority: ${priority}\nCaller: ${callerNumber}\nCall ID: ${callContext.callId}\n\n${notes || ''}`
    }
  });
  
  return {
    toolCallId: toolCall.id,
    result: `Done! I've created a ${priority} priority task: "${title}"`
  };
}
```

---

### 2. schedule_reminder

**Purpose:** Set a reminder for a specific time

**VAPI Tool Definition:**
```json
{
  "type": "function",
  "function": {
    "name": "schedule_reminder",
    "description": "Schedule a reminder. Use when caller says 'remind me', 'don't forget', or mentions a specific time.",
    "parameters": {
      "type": "object",
      "properties": {
        "reminder": {
          "type": "string",
          "description": "What to be reminded about"
        },
        "when": {
          "type": "string",
          "description": "When to send reminder (e.g., 'in 2 hours', 'tomorrow at 9am', 'next Monday')"
        }
      },
      "required": ["reminder", "when"]
    }
  },
  "server": {
    "url": "https://rateright-growth-production.up.railway.app/api/vapi/tools",
    "credentialId": "cred_rateright_webhook"
  }
}
```

**Webhook Handler:**
```javascript
async function handleScheduleReminder(toolCall, callContext) {
  const { reminder, when } = toolCall.arguments;
  
  // Parse natural language time with chrono-node
  const parsedTime = chrono.parseDate(when, new Date(), { timezone: 'Australia/Sydney' });
  
  if (!parsedTime) {
    return {
      toolCallId: toolCall.id,
      result: `I couldn't understand the time "${when}". Can you say it differently?`
    };
  }
  
  // Create Google Calendar event with reminder
  const event = await googleCalendar.events.insert({
    calendarId: 'primary',
    resource: {
      summary: `📞 Reminder: ${reminder}`,
      description: `From call with ${callContext.customer?.number}`,
      start: { dateTime: parsedTime.toISOString() },
      end: { dateTime: new Date(parsedTime.getTime() + 15*60000).toISOString() },
      reminders: { useDefault: false, overrides: [{ method: 'popup', minutes: 0 }] }
    }
  });
  
  const timeStr = parsedTime.toLocaleString('en-AU', { 
    weekday: 'long', hour: 'numeric', minute: '2-digit' 
  });
  
  return {
    toolCallId: toolCall.id,
    result: `Reminder set for ${timeStr}: "${reminder}"`
  };
}
```

---

### 3. lookup_lead

**Purpose:** Look up caller info from Growth Engine

**VAPI Tool Definition:**
```json
{
  "type": "function",
  "function": {
    "name": "lookup_lead",
    "description": "Look up information about the caller in our system. Use at the start of calls to personalize the conversation.",
    "parameters": {
      "type": "object",
      "properties": {
        "phone": {
          "type": "string",
          "description": "Phone number to look up (optional - uses caller's number if not provided)"
        }
      }
    }
  },
  "server": {
    "url": "https://rateright-growth-production.up.railway.app/api/vapi/tools",
    "credentialId": "cred_rateright_webhook"
  }
}
```

**Webhook Handler:**
```javascript
async function handleLookupLead(toolCall, callContext) {
  const phone = toolCall.arguments.phone || callContext.customer?.number;
  
  // Query Growth Engine database
  const { data: lead } = await supabase
    .from('leads')
    .select('name, company, status, last_contact, notes')
    .eq('phone', normalizePhone(phone))
    .single();
  
  if (!lead) {
    return {
      toolCallId: toolCall.id,
      result: "This is a new caller - no previous record found."
    };
  }
  
  const daysSince = Math.floor((Date.now() - new Date(lead.last_contact)) / 86400000);
  
  return {
    toolCallId: toolCall.id,
    result: `This is ${lead.name}${lead.company ? ` from ${lead.company}` : ''}. ` +
            `Status: ${lead.status}. Last contact: ${daysSince} days ago. ` +
            `Notes: ${lead.notes || 'None'}`
  };
}
```

---

### 4. message_michael

**Purpose:** Send an urgent message to Michael during/after a call

**VAPI Tool Definition:**
```json
{
  "type": "function",
  "function": {
    "name": "message_michael",
    "description": "Send an urgent message to Michael. Use when caller needs to speak to a human, has a complaint, or mentions something time-sensitive.",
    "parameters": {
      "type": "object",
      "properties": {
        "message": {
          "type": "string",
          "description": "The message to send"
        },
        "urgency": {
          "type": "string",
          "enum": ["normal", "urgent"],
          "description": "How urgent is this"
        }
      },
      "required": ["message"]
    }
  },
  "server": {
    "url": "https://rateright-growth-production.up.railway.app/api/vapi/tools",
    "credentialId": "cred_rateright_webhook"
  }
}
```

**Webhook Handler:**
```javascript
async function handleMessageMichael(toolCall, callContext) {
  const { message, urgency = 'normal' } = toolCall.arguments;
  const callerNumber = callContext.customer?.number || 'Unknown';
  
  // Send via Telegram (using Clawdbot's existing bot)
  const emoji = urgency === 'urgent' ? '🚨' : '📞';
  const telegramMessage = `${emoji} **Phone Call Alert**\n\n` +
    `From: ${callerNumber}\n` +
    `Message: ${message}\n` +
    `Call ID: ${callContext.callId}`;
  
  await fetch(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      chat_id: process.env.MICHAEL_TELEGRAM_ID, // Michael's personal chat
      text: telegramMessage,
      parse_mode: 'Markdown'
    })
  });
  
  return {
    toolCallId: toolCall.id,
    result: urgency === 'urgent' 
      ? "I've sent an urgent message to Michael. He should see it right away."
      : "I've let Michael know. He'll follow up with you."
  };
}
```

---

## Implementation Plan

### Phase 1: Basic Setup (1-2 hours)

1. **Add webhook route to Growth Engine**
   ```
   POST /api/vapi/tools
   ```

2. **Implement router**
   ```javascript
   // api/vapi/tools/route.ts
   export async function POST(req: Request) {
     const body = await req.json();
     const { toolCallList } = body.message;
     
     const results = await Promise.all(
       toolCallList.map(toolCall => routeToolCall(toolCall, body.message.call))
     );
     
     return Response.json({ results });
   }
   ```

3. **Add auth middleware**

4. **Configure VAPI credential**

### Phase 2: Core Tools (2-3 hours)

1. `message_michael` - Simplest, test the pipeline
2. `create_todo` - Google Tasks integration
3. `lookup_lead` - Supabase query

### Phase 3: Advanced (1-2 hours)

1. `schedule_reminder` - Natural language time parsing
2. Add the tools to VAPI assistant config
3. Test with real calls

### Phase 4: Polish

1. Error handling & logging
2. Call transcript logging to Supabase
3. Analytics on tool usage

---

## Environment Variables Needed

```bash
# Add to Growth Engine .env
VAPI_WEBHOOK_TOKEN=<generate-32-char-token>
GOOGLE_TASKS_CREDENTIALS=<service-account-json>
TELEGRAM_BOT_TOKEN=<existing-clawdbot-token>
MICHAEL_TELEGRAM_ID=<michael-chat-id>
```

---

## Security Considerations

| Risk | Mitigation |
|------|------------|
| Unauthorized webhook calls | Bearer token auth + VAPI IP whitelist |
| Sensitive data in logs | Redact phone numbers in production logs |
| Injection via tool args | Sanitize all inputs before DB/API calls |
| Rate limiting | Max 10 tool calls per call session |
| Credential exposure | All secrets in Railway env vars |

### IP Whitelist (Optional)

VAPI webhook calls come from known IPs. Can add allowlist if needed:
```javascript
const VAPI_IPS = ['52.x.x.x', '54.x.x.x']; // Get from VAPI docs
```

---

## Testing

### Local Testing with ngrok

```bash
# Terminal 1
ngrok http 3000

# Terminal 2  
vapi listen --forward-to localhost:3000/api/vapi/tools
```

### Test Payload

```bash
curl -X POST http://localhost:3000/api/vapi/tools \
  -H "Authorization: Bearer $VAPI_WEBHOOK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "type": "tool-calls",
      "toolCallList": [{
        "id": "test_123",
        "name": "message_michael",
        "arguments": {"message": "Test from curl", "urgency": "normal"}
      }],
      "call": {"id": "test-call", "customer": {"number": "+61400000000"}}
    }
  }'
```

---

## Quick Reference

| Tool | Trigger Phrases | Action |
|------|-----------------|--------|
| create_todo | "remind me to...", "I need to...", "don't forget..." | Creates Google Task |
| schedule_reminder | "remind me at 3pm", "next Tuesday" | Creates calendar event |
| lookup_lead | (auto at call start) | Returns caller context |
| message_michael | "I need to speak to someone", "urgent", "complaint" | Sends Telegram message |

---

## Next Steps

1. ✅ Spec complete
2. [ ] Add `/api/vapi/tools` route to Growth Engine
3. [ ] Test with `message_michael` first
4. [ ] Create VAPI Custom Credential
5. [ ] Add tools to VAPI assistant
6. [ ] Test with real call
