Example Integration
A minimal Node.js backend implementing all three callbacks with signature verification.
Server
server.js
const express = require('express');
const crypto = require('crypto');
const app = express();
const WEBHOOK_SECRET = process.env.VEEVO_WEBHOOK_SECRET;
// Use express.raw() so we can verify the signature against the raw body
app.use('/calls', express.raw({ type: 'application/json' }));
// Verify X-Veevo-Signature on every callback
function veevoAuth(req, res, next) {
const signature = req.headers['x-veevo-signature'];
if (!signature) return res.status(401).json({ error: 'Missing signature' });
const rawBody = req.body.toString('utf-8');
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).json({ error: 'Invalid signature' });
}
req.body = JSON.parse(rawBody);
next();
}
app.post('/calls/start', veevoAuth, (req, res) => {
res.json({
twilioAccountSid: process.env.TWILIO_ACCOUNT_SID,
twilioAuthToken: process.env.TWILIO_AUTH_TOKEN,
openaiApiKey: process.env.OPENAI_API_KEY,
systemPrompt: 'You are a helpful assistant. Greet the caller warmly.',
voice: 'marin',
onToolCallUrl: `${process.env.BASE_URL}/calls/tool`,
tools: [
{
type: 'function',
name: 'check_availability',
description: 'Check if a date has availability',
parameters: {
type: 'object',
properties: {
date: { type: 'string', description: 'The date to check' },
},
required: ['date'],
},
},
],
});
});
app.post('/calls/tool', veevoAuth, (req, res) => {
const { toolName, arguments: args } = req.body;
if (toolName === 'check_availability') {
res.json({ result: `We have openings on ${args.date}.` });
} else {
res.json({ result: `Unknown tool: ${toolName}` });
}
});
app.post('/calls/end', veevoAuth, (req, res) => {
console.log(`Call ${req.body.callSid} ended - ${req.body.durationSeconds}s`);
console.log(`Caller: ${req.body.callerNumber} → ${req.body.calledNumber}`);
console.log(`Cost: $${req.body.costBreakdown?.totalCost}`);
res.json({ received: true });
});
app.listen(3001);Environment Variables
.env
TWILIO_ACCOUNT_SID=AC...
TWILIO_AUTH_TOKEN=...
OPENAI_API_KEY=sk-...
BASE_URL=https://your-backend.com
VEEVO_WEBHOOK_SECRET=whsec_...Find your webhook signing secret in the dashboard under API Keys & Secrets.
Defining Tools
Tools follow the OpenAI function calling schema. Return them in the tools array from your onCallStart response.
Tool definition example
{
"type": "function",
"name": "schedule_appointment",
"description": "Schedule an appointment for the caller",
"parameters": {
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "Preferred date (e.g. 'April 15, 2026')"
},
"time": {
"type": "string",
"description": "Preferred time (e.g. '2:00 PM')"
},
"notes": {
"type": "string",
"description": "Any additional notes from the caller"
}
},
"required": ["date", "time"]
}
}When the AI invokes a tool, Veevo POSTs to your onToolCallUrl with the tool name and arguments. Return a result string that the AI will use to continue the conversation.
What Happens on a Call
1Caller dials your Twilio number
2Twilio sends the call to Veevo's engine URL
3Veevo calls your onCallStart endpoint (signed with X-Veevo-Signature)
4Your backend verifies the signature, then returns credentials, prompt, and tools
5Veevo connects the caller to OpenAI's Realtime API using your credentials
6When the AI invokes a tool, Veevo calls your onToolCall endpoint (also signed)
7When the call ends, Veevo calls your onCallEnd endpoint with the transcript and costs