Vodia PBX JavaScript Voice Agents Functions Reference
Call Object Functions
| Function | Description | Parameters | Example |
|---|---|---|---|
call.say() | Text-to-speech playback | text (string) or object with text, optional background, and optional callback properties | call.say('Welcome to our service'); or call.say({ text: 'Hello', background: false, callback: function() { call.hangup(); } }); |
call.dtmf() | Capture DTMF digits | Callback function | call.dtmf(onDtmfHandler); |
call.play() | Play audio | Object with audio, codec, direction properties or url | call.play({ direction: "out", codec: codec, audio: audio }); |
call.stream() | Start/stop audio streaming | Object with codec, interval, callback or empty to stop | call.stream({ codec: "g711_ulaw", interval: 0.5, callback: streamHandler }); |
call.transfer() | Transfer call to extension or control attended transfer | Extension number (string) or object with action property ('accept', 'reject', 'blind', 'hold', 'resume') | call.transfer('700'); or call.transfer({ action: 'accept' }); or call.transfer({ action: 'hold' }); or call.transfer({ action: 'resume' }); |
call.find() | Find active calls | Type (string), extension (string) | const calls = call.find('calls', call.extension); |
call.dial() | Initialize AI integration or make outbound call. Returns a call ID that can be used with call.control() | Provider name (string) or object with call parameters | call.dial('openai'); or var callid = call.dial({ account: '88', dest: '4001', from: caller, cobj: callObj, ivraction: 'attendant' }); |
call.control() | Control a call previously initiated with call.dial(), e.g. cancel it before it is answered | Object with action property (currently 'cancel') and callid (the ID returned by call.dial()) | call.control({ action: 'cancel', callid: callid }); |
call.mute() | Mute the call | None | call.mute(); |
call.hangup() | Hang up the call | None | call.hangup(); |
call.sms() | Send SMS message | String (text only) or object with text, from, to, name properties (optional) | call.sms("Hello"); or call.sms({ text: "hello", from: "+16171234567", to: "+12121234567", name: "Customer" }); |
call.log() | Log message to console | Message (string) | call.log("Script started"); |
call.http() | Register HTTP callback handler for webhooks | Callback function | call.http(onHttpHandler); |
Call Object Properties
| Property | Description | Type | Example |
|---|---|---|---|
call.callid | Unique identifier for the call | string | var callId = call.callid; |
call.lang | Language code for the call | string | var language = call.lang; |
call.extension | Current extension handling the call | string | var ext = call.extension; |
call.orig_from | Original FROM header of the call | string | var from = call.orig_from; |
call.ivraction | Voice Agents action type (e.g., 'attendant') | string | if (call.ivraction == 'attendant') { ... } |
System Object Functions
| Function | Description | Parameters | Example |
|---|---|---|---|
system.http() | Make HTTP request | Object with method, url, header, body, callback, timeout | system.http({ method: 'POST', url: 'http://...', callback: handler }); |
system.email() | Send email | Object with to, from, subject, body | system.email({ to: recipient, from: sender, subject: 'Test', body: 'Message' }); |
system.parseEmailAdr() | Parse email address | Email string | var addr = system.parseEmailAdr('test@example.com'); |
Tables Object Functions
| Function | Description | Parameters | Example |
|---|---|---|---|
tables['cobjs'].get() | Get call object data | callid, field name (e.g., 'from', 'to') | var from = tables['cobjs'].get(call.callid, 'from'); |
tables['adrbook'].search() | Search address book | Field name, search value | var results = tables['adrbook'].search('number', phoneNumber); |
tables['adrbook'].get() | Get address book entry | Entry ID, field name or '*' for all | var fax = tables['adrbook'].get(entryId, 'fax'); |
tables['adrbook'].count() | Count address book entries | None | var count = tables['adrbook'].count(); |
AddressBook Class
While tables['adrbook'] provides low-level read access, the AddressBook class offers a convenient way to search, read, create, update and delete address book entries from a Voice Agents script.
Writes performed with the AddressBook class during a call are reflected immediately — for example, an updated caller name will show up right away on the phone display and in the user front end.
| Function | Description | Parameters | Example |
|---|---|---|---|
new AddressBook() | Create an address book object for a tenant (and optionally a user) | domain (string or number, required), user (string or number, optional) | var adr = new AddressBook('localhost'); |
adr.search() | Search for an entry. Without a field name, it tries number, mobile and fax. Searchable fields: number, mobile, fax, speed, tag, cmc, category. The callback receives the entry ID or false if not found | field (string, optional), search value (string), callback function | adr.search('number', from, function(id) { ... }); or adr.search(from, function(id) { ... }); |
adr.get() | Read entry content. Pass a field name (string) for one column, an array of field names for several, or omit for all columns as one object | id, field (string, array or omitted) | var company = adr.get(id, 'company'); |
adr.update() | Update an entry. fields is an object with column names and values. Display fields for numbers are generated automatically if not provided | id, fields (object), callback function (optional) | adr.update(id, { category: 'vip' }, function() { ... }); |
adr.create() | Create a new entry, then apply the provided fields | fields (object), callback function (optional) | adr.create({ first: 'Joe', name: 'Doe', number: '+16173998147' }, function(id) { ... }); |
adr.erase() | Delete an entry | id, callback function (optional) | adr.erase(id, function() { ... }); |
Common address book columns include number, mobile, fax, first, name, company, email, tag, cmc, comment, agent, dnc, melody and category. For the complete column list, see the Vodia Backend JavaScript Documentation.
AddressBook.search() supports only the indexed fields listed above — it does not search display_number. To match on display_number (the number as originally entered), use the low-level tables['adrbook'].search('display_number', value) instead, as shown in the Smart Address Book Transfer example.
On multi-tenant systems, pass the correct tenant to the constructor rather than hardcoding 'localhost'. The tenant name can be extracted from the to header of the call — see the Webhook Smart Routing example for a complete getDomainFromCall() implementation.
WebSocket Functions
| Function | Description | Parameters | Example |
|---|---|---|---|
new Websocket() | Create WebSocket connection | WebSocket URL (string) | var ws = new Websocket("wss://api.example.com"); |
ws.header() | Set WebSocket headers | Array of header objects with name, value, optional secret | ws.header([{ name: "Authorization", value: "Bearer " + token, secret: true }]); |
ws.on() | Register event handler | Event name ('open', 'close', 'message'), callback function | ws.on('message', function(message) { ... }); |
ws.send() | Send data through WebSocket | Data (string, typically JSON) | ws.send(JSON.stringify(data)); |
ws.connect() | Connect WebSocket | None | ws.connect(); |
ws.close() | Close WebSocket connection | None | ws.close(); |
Utility Functions
| Function | Description | Parameters | Example |
|---|---|---|---|
toBase64String() | Convert data to base64 | Data (buffer/string) | var b64 = toBase64String(audioData); |
fromBase64String() | Convert base64 to data | Base64 string | var data = fromBase64String(b64String); |
setTimeout() | Execute function after delay | Callback function, delay in milliseconds | setTimeout(function() { call.hangup(); }, 5000); |
clearTimeout() | Cancel scheduled timeout | Timer reference | clearTimeout(timer); |
console.log() | Log to console | Message (string) | console.log("Debug message"); |
Common Patterns and Usage
DTMF Collection Pattern
var digits = '';
call.dtmf(onDtmf);
function onDtmf(digit) {
digits += digit;
if (digits.length == 4) {
processDigits(digits);
digits = '';
}
}
HTTP Request Pattern
var args = {
method: 'POST',
url: 'http://example.com/webhook',
header: [{ name: 'Content-Type', value: 'application/json' }],
body: JSON.stringify({ key: 'value' }),
callback: function(code, response, headers) {
console.log('Response code: ' + code);
var data = JSON.parse(response);
// Process response
}
};
system.http(args);
WebSocket Streaming Pattern (OpenAI Realtime)
var ws = new Websocket("wss://api.openai.com/v1/realtime?model=...");
ws.on('open', function() {
console.log("Connected");
});
ws.on('message', function(message) {
var msg = JSON.parse(message);
if (msg.type == "response.audio.delta") {
var audio = fromBase64String(msg.delta);
call.play({ direction: "out", codec: "g711_ulaw", audio: audio });
}
});
call.stream({
codec: "g711_ulaw",
interval: 0.5,
callback: function(audio) {
ws.send(JSON.stringify({
type: "input_audio_buffer.append",
audio: toBase64String(audio)
}));
}
});
ws.connect();
Attended Transfer Pattern
// Step 1: In the originating call - find active calls
const calls = call.find('calls', call.extension);
if (calls.length > 0) {
const c = calls[0];
// Step 2: Hold the current call
call.transfer({ action: 'hold' });
// Step 3: Dial the destination with attended transfer parameters
// call.dial() returns a call ID that can be used with call.control()
var callid = call.dial({
account: '88', // Account/trunk to use
dest: '403', // Destination extension
from: '', // Can be empty or original caller
cobj: c.cobj, // Original call object to connect
ivraction: info // Information to pass (or 'attendant')
});
}
// Step 4: In the destination Voice Agents, handle the attended transfer
if (call.ivraction && call.ivraction == 'attendant') {
// Extract original caller information
const origname = call.orig_from.split('"')[1];
// Prompt destination user and get response
// Based on their response:
// Option A: Accept the transfer (connects both calls)
call.transfer({ action: 'accept' });
// Option B: Reject the transfer (returns caller to original call)
call.transfer({ action: 'reject' });
}
// Step 5: Handle transfer result via HTTP callback
call.http(function(args) {
var body = JSON.parse(args.body);
if (body.type == 'att_transfer') {
if (body.result == 'true') {
console.log('Attended transfer succeeded');
call.say({
text: 'Transfer successful',
callback: function() { call.hangup(); }
});
} else {
console.log('Attended transfer failed');
call.say({
text: 'Transfer failed',
callback: function() { call.hangup(); }
});
}
}
});
Attended Transfer with Timeout (Cancel) Pattern
When the voice agent dials a destination for an attended transfer, the destination may never accept or reject the call. Use the call ID returned by call.dial() together with call.control() to cancel the outbound leg and call.transfer({ action: 'resume' }) to take back the held call.
// Hold the current call and dial the transfer destination
call.transfer({ action: 'hold' });
var callid = call.dial({
account: '851',
dest: '403',
from: 'Some test',
cobj: call.callid,
ivraction: 'att_transfer'
});
// If the destination has not accepted or rejected within 20 seconds,
// cancel the dialed call and resume the held call
var timer = setTimeout(function() {
call.transfer({ action: 'resume' });
call.control({ action: 'cancel', callid: callid });
call.say('The party is not available right now.');
}, 20000);
// If the transfer completes in time, clear the timer in the
// att_transfer HTTP callback with clearTimeout(timer)
Table Lookup Pattern
// Get caller information (raw values are full SIP URIs)
var fromRaw = tables['cobjs'].get(call.callid, 'from');
var toRaw = tables['cobjs'].get(call.callid, 'to');
// Extract the number before searching — address book entries
// store numbers in global format (e.g. +61433997299), not SIP URIs
var match = fromRaw.match(/sip:(\+?\d+)@/);
var from = match ? match[1] : fromRaw;
// Search address book
var results = tables['adrbook'].search('number', from);
if (results && results.length > 0) {
var entryId = results[0];
var extension = tables['adrbook'].get(entryId, 'fax');
call.transfer(extension);
}
Address Book Read/Write Pattern
// Look up the caller and tag them as a VIP; if unknown, create a new entry.
// Changes are visible immediately on the phone display and user front end.
function extractPhoneNumber(sipUri) {
if (!sipUri) return '';
var match = sipUri.match(/sip:(\+?\d+)@/);
return (match && match[1]) ? match[1] : sipUri;
}
var fromRaw = tables['cobjs'].get(call.callid, 'from');
var toRaw = tables['cobjs'].get(call.callid, 'to');
var from = extractPhoneNumber(fromRaw);
// Derive the tenant from the 'to' header (multi-tenant safe)
var domainMatch = toRaw.match(/@([^>]+)/);
var currentDomain = domainMatch ? domainMatch[1] : 'localhost';
var adr = new AddressBook(currentDomain);
adr.search('number', from, function(id) {
if (id) {
adr.update(id, { category: 'vip', comment: 'Called the priority line' }, function() {
call.transfer('400');
});
} else {
adr.create({ number: from, name: 'Unknown Caller', category: 'new' }, function(newId) {
call.transfer('501');
});
}
});
Call Transfer with Delay Pattern
call.say('Transferring your call now.');
setTimeout(function() {
call.transfer('700');
}, 3000);
SMS Sending Pattern
// Simple SMS
call.sms("Thank you for calling. Your reference number is 12345.");
// Advanced SMS with custom from/to numbers
call.sms({
text: "Your appointment is confirmed for tomorrow at 2 PM.",
from: "+16171234567",
to: "+12121234567",
name: "Customer"
});
AI Integration Pattern
// Initialize AI provider for the call
// Set up HTTP callback handler
call.http(onHttpCallback);
function onHttpCallback(args) {
var body = JSON.parse(args.body);
if (body.type == 'realtime.call.incoming') {
// Handle AI call setup
setupAICall(body.data.call_id);
}
}
call.dial('openai');
HTTP Callback Handler Pattern
// Register handler for incoming HTTP callbacks (webhooks)
call.http(function(args) {
console.log('Received HTTP callback');
console.log(JSON.stringify(args));
var body = JSON.parse(args.body);
// Process different callback types
if (body.type == 'realtime.call.incoming') {
var callid = body.data.call_id;
// Accept the incoming call
system.http({
method: 'POST',
url: 'https://api.openai.com/v1/realtime/calls/' + callid + '/accept',
header: [
{ name: 'Authorization', value: 'Bearer ' + apiKey, secret: true },
{ name: 'Content-Type', value: 'application/json' }
],
body: JSON.stringify({
type: "realtime",
model: "gpt-realtime",
instructions: "You are a helpful assistant."
}),
callback: function(code, response, headers) {
console.log('Call accepted with code: ' + code);
// Continue processing
}
});
}
else if (body.type == 'att_transfer') {
// Handle attended transfer result
if (body.result == 'true') {
console.log('Attended transfer succeeded');
call.say({ text: 'Transfer successful', callback: function() { call.hangup(); }});
} else {
console.log('Attended transfer failed');
call.say({ text: 'Transfer failed', callback: function() { call.hangup(); }});
}
}
});
Text-to-Speech with Callback Pattern
// Execute code after TTS completes
call.say({
text: 'Please wait while I transfer your call.',
background: false,
callback: function() {
// This runs after TTS finishes
call.transfer('700');
}
});
// Chain multiple TTS messages
call.say({
text: 'Thank you for calling.',
callback: function() {
call.say({
text: 'Goodbye.',
callback: function() {
call.hangup();
}
});
}
});
Audio Codecs
Supported audio codecs for call.play() and call.stream():
"g711_ulaw"- G.711 μ-law (most common)"g711_alaw"- G.711 A-law"pcm16"- 16-bit PCM"mp3"- MP3 (for playback only)
Version Information
These functions are documented based on Vodia PBX version 69.5.19+. call.control(), the call ID return value of call.dial() and the 'resume' transfer action require a current v70 build. Function availability and behavior may vary in different versions.
For more information on Vodia's JavaScript capabilities, refer to: Vodia Backend JavaScript Documentation