@veroai/voice
v0.1.1
Published
VeroAI Voice SDK - Build voice applications with webhooks, real-time call control, and WebRTC
Downloads
214
Maintainers
Readme
@veroai/voice
VeroAI Voice SDK - Build voice applications with webhooks and real-time call control.
Installation
npm install @veroai/voiceQuick Start
import { VoiceResponse, parseIncomingCall } from '@veroai/voice';
// Handle incoming call webhook
export async function POST(req: Request) {
const body = await req.json();
const call = parseIncomingCall(body);
console.log(`Incoming call from ${call.from} to ${call.to}`);
const response = new VoiceResponse()
.say({ text: 'Welcome to Acme Corp!' })
.gather({
input: ['dtmf', 'speech'],
numDigits: 1,
timeout: 5,
say: { text: 'Press 1 for sales, or 2 for support.' },
actionHook: '/api/voice/menu'
})
.say({ text: "We didn't receive any input. Goodbye!" })
.hangup();
return Response.json(response.toJSON());
}Voice Response Builder
The VoiceResponse class provides a fluent API for building call flows:
Speaking Text
// Simple
response.say('Hello, world!');
// With options
response.say({
text: 'Welcome!',
synthesizer: {
vendor: 'elevenlabs',
voice: 'rachel',
language: 'en-US'
},
loop: 2
});Playing Audio
response.play('https://example.com/welcome.mp3');
response.play({
url: 'https://example.com/music.mp3',
loop: 3,
timeoutSecs: 30
});Gathering Input
// Collect DTMF digits
response.gather({
input: ['dtmf'],
numDigits: 4,
timeout: 10,
say: { text: 'Please enter your PIN.' },
actionHook: '/api/voice/verify-pin'
});
// Collect speech
response.gather({
input: ['speech'],
timeout: 5,
say: { text: 'How can I help you today?' },
recognizer: {
vendor: 'deepgram',
language: 'en-US',
hints: ['sales', 'support', 'billing']
},
actionHook: '/api/voice/intent'
});
// Collect either
response.gather({
input: ['dtmf', 'speech'],
numDigits: 1,
say: { text: 'Press 1 or say "sales" for sales.' },
actionHook: '/api/voice/route'
});Dialing
// Dial a phone number
response.dial({
target: [{ type: 'phone', number: '+15551234567' }],
callerId: '+15559876543',
timeout: 30
});
// Dial a SIP endpoint
response.dial({
target: [{ type: 'sip', sipUri: 'sip:[email protected]' }],
timeout: 20
});
// Dial multiple targets (first to answer wins)
response.dial({
target: [
{ type: 'phone', number: '+15551111111' },
{ type: 'phone', number: '+15552222222' }
],
timeout: 30,
dialMusic: 'https://example.com/hold-music.mp3'
});Conference Calls
response.conference({
name: 'team-standup',
beep: true,
maxParticipants: 10,
statusHook: '/api/voice/conference-events'
});Recording
// Start recording
response.record({
action: 'start',
stereo: true,
recordingStatusHook: '/api/voice/recording-ready'
});
// Stop recording
response.record({ action: 'stop' });Transcription
response.transcribe({
enable: true,
transcriptionHook: '/api/voice/transcription',
recognizer: {
vendor: 'deepgram',
language: 'en-US'
}
});Other Verbs
// Pause
response.pause(2); // 2 seconds
response.pause({ length: 5 });
// Send DTMF
response.dtmf('1234#');
// Redirect to another webhook
response.redirect('/api/voice/other-handler');
// Hang up
response.hangup();Webhook Parsers
Parse incoming webhook payloads with type safety:
import {
parseIncomingCall,
parseCallStatus,
parseGatherResult,
parseTranscription
} from '@veroai/voice';
// Incoming call
const call = parseIncomingCall(body);
console.log(call.from, call.to, call.callId);
// Call status update
const status = parseCallStatus(body);
console.log(status.status, status.duration);
// Gather result
const gather = parseGatherResult(body);
if (gather.digits) {
console.log('DTMF:', gather.digits);
} else if (gather.speech) {
console.log('Speech:', gather.speech.text);
}
// Transcription
const transcript = parseTranscription(body);
console.log(transcript.transcript, transcript.isFinal);Signature Verification
Verify webhook signatures to ensure requests are authentic:
import { verifySignature, verifySignatureAsync } from '@veroai/voice';
// Node.js
const isValid = verifySignature(
JSON.stringify(body),
request.headers['x-veroai-signature'],
process.env.WEBHOOK_SECRET
);
// Edge runtime (async)
const isValid = await verifySignatureAsync(
JSON.stringify(body),
request.headers.get('x-veroai-signature'),
process.env.WEBHOOK_SECRET
);TypeScript Types
All types are exported for use in your application:
import type {
IncomingCallPayload,
CallStatusPayload,
GatherResultPayload,
GatherOptions,
DialOptions,
Synthesizer,
Recognizer
} from '@veroai/voice';Supported TTS Vendors
google- Google Cloud Text-to-Speechaws- Amazon Pollyazure- Microsoft Azure Speechelevenlabs- ElevenLabsdeepgram- Deepgram Aurawellsaid- WellSaid Labs
Supported STT Vendors
google- Google Cloud Speech-to-Textaws- Amazon Transcribeazure- Microsoft Azure Speechdeepgram- Deepgramassembly- AssemblyAI
Examples
IVR Menu
import { VoiceResponse, parseIncomingCall, parseGatherResult } from '@veroai/voice';
// /api/voice/incoming
export async function handleIncoming(req: Request) {
return new VoiceResponse()
.say({ text: 'Welcome to Acme Corp!' })
.gather({
input: ['dtmf'],
numDigits: 1,
say: { text: 'Press 1 for sales, 2 for support, or 3 for billing.' },
actionHook: '/api/voice/menu'
})
.say({ text: 'Goodbye!' })
.hangup()
.toJSON();
}
// /api/voice/menu
export async function handleMenu(req: Request) {
const body = await req.json();
const result = parseGatherResult(body);
const response = new VoiceResponse();
switch (result.digits) {
case '1':
response
.say({ text: 'Connecting you to sales.' })
.dial({ target: [{ type: 'phone', number: '+15551111111' }] });
break;
case '2':
response
.say({ text: 'Connecting you to support.' })
.dial({ target: [{ type: 'phone', number: '+15552222222' }] });
break;
case '3':
response
.say({ text: 'Connecting you to billing.' })
.dial({ target: [{ type: 'phone', number: '+15553333333' }] });
break;
default:
response
.say({ text: 'Invalid selection. Please try again.' })
.redirect('/api/voice/incoming');
}
return response.toJSON();
}Call Forwarding with Voicemail
import { VoiceResponse } from '@veroai/voice';
export async function handleCall(req: Request) {
return new VoiceResponse()
.dial({
target: [{ type: 'phone', number: '+15551234567' }],
timeout: 20,
actionHook: '/api/voice/dial-result'
})
.toJSON();
}
export async function handleDialResult(req: Request) {
const body = await req.json();
// If no answer, go to voicemail
if (body.dial_status !== 'completed') {
return new VoiceResponse()
.say({ text: 'Sorry, no one is available. Please leave a message after the beep.' })
.record({
action: 'start',
recordingStatusHook: '/api/voice/voicemail-saved'
})
.pause(60) // Max 60 second message
.hangup()
.toJSON();
}
return new VoiceResponse().hangup().toJSON();
}License
MIT
