veevo.ai

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