IVR JS Example (Hotel Reception)
Demonstrating Vodia's Javascript IVR capabilities, this example script includes TTS output, OpenAI integration to provide hotel reception functionality, and Caller ID handling where collected caller ID and guest details are sent to an external application for further processing(for e.g. Hotel's Booking system).
Scenario:
- TTS Output: Delivering a welcome message via Vodia's Text-to-Speech.
- OpenAI Integration: Providing hotel reception functionality.
- Guest Information Collection: Gathering details such as name, country, number of guests, and check-in/check-out dates.
- External Application Communication: Transmitting the Caller ID and collected guest details in JSON format to an external application upon detection of relevant information in the transcript. This could be the Hotel's Booking system
- Intelligent Call Routing: Transferring the call to the appropriate department (e.g., Room Information, Reservations, Restaurant).
tip
Please customize these example instruction prompts for OpenAI to align with your specific needs.
//
// OpenAI integration for Vodia Hotel
//
// (C) Vodia Networks 2025
//
// This file is property of Vodia Networks Inc. All rights reserved.
// For more information mail Vodia Networks Inc., info@vodia.com.
//
'use strict';
// API key for OpenAI
var secret = "open_ai_key";
// Audio codec to use
var codec = "g711_ulaw";
// Get caller number and IVR called
var from = tables['cobjs'].get(call.callid, 'from');
var to = tables['cobjs'].get(call.callid, 'to');
// Status flags
var greeted = false;
var transferComplete = false;
var responseComplete = false;
// Storage for transfer information
var pendingTransfer = null;
var pendingStartDate = null;
var pendingLastDate = null;
// New guest information fields
var pendingGuestName = null;
var pendingGuestCountry = null;
var pendingGuestCount = null;
// Set timeout for the call (5 minutes)
var timer = setTimeout(function() {
if (!transferComplete) {
console.log("Call timeout reached (5 minutes), transferring to default extension 700");
call.transfer('700');
}
}, 300000);
// Function to send booking date information and guest details to webhook
function send(start_date, last_date, guest_name, guest_country, guest_count, from, to) {
console.log("WEBHOOK: Preparing to send reservation data");
var body = JSON.stringify({
startdate: start_date,
enddate: last_date,
name: guest_name,
country: guest_country,
guests: guest_count,
callernumber: from,
callednumber: to
});
console.log("WEBHOOK: Request body: " + body);
var args = {
method: 'POST',
url: 'https://external_web_server/webhook',
header: [ { name: 'Content-Type', value: 'application/json' } ]
};
if (body) {
args.body = body;
}
console.log("WEBHOOK: Sending HTTP request to webhook");
try {
system.http(args);
console.log("WEBHOOK: HTTP request sent successfully");
} catch (e) {
console.log("WEBHOOK ERROR: " + e.message);
}
}
// Helper function to execute transfer with proper logging
function executeTransfer() {
if (transferComplete) return;
// Add date information to the log if available
if (pendingStartDate) console.log("Booking start_date: " + pendingStartDate);
if (pendingLastDate) console.log("Booking last_date: " + pendingLastDate);
if (pendingGuestName) console.log("Guest name: " + pendingGuestName);
if (pendingGuestCountry) console.log("Guest country: " + pendingGuestCountry);
if (pendingGuestCount) console.log("Number of guests: " + pendingGuestCount);
// Send booking information to webhook if we have any reservation data
if (pendingTransfer == "701" || // Only for reservations department transfers
pendingStartDate || pendingLastDate ||
pendingGuestName || pendingGuestCountry || pendingGuestCount) {
console.log("Sending reservation data to webhook");
// If data is missing, use defaults or empty values
var startDate = pendingStartDate || "01/01/2025";
var endDate = pendingLastDate || "02/01/2025";
var guestName = pendingGuestName || "Unknown";
var guestCountry = pendingGuestCountry || "Unknown";
var guestCount = pendingGuestCount || "1";
send(startDate, endDate, guestName, guestCountry, guestCount, from, to);
}
// Mark transfer as complete to prevent multiple transfers
transferComplete = true;
// Clear the timeout as we're handling the transfer now
if (timer) clearTimeout(timer);
// Add a small final delay to ensure any in-progress audio completes
setTimeout(function() {
// Execute the transfer
call.mute();
try {
console.log("Transferring call to extension: " + pendingTransfer);
call.transfer(pendingTransfer);
console.log("Transfer command executed");
} catch (e) {
console.log("Transfer error: " + e.message);
}
}, 500);
}
// Open the websocket connection
var ws = new Websocket("wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01");
// Set up headers for the connection
ws.header([
{ name: "Authorization", value: "Bearer " + secret, secret: true },
{ name: "Cache-Control", value: "no-cache" },
{ name: "Pragma", value: "no-cache" },
{ name: "Sec-Fetch-Dest", value: "websocket" },
{ name: "Sec-Fetch-Mode", value: "websocket" },
{ name: "Sec-Fetch-Site", value: "same-site" },
{ name: "Sec-WebSocket-Protocol", value: "realtime" },
{ name: "OpenAI-Beta", value: "realtime=v1" },
{ name: "User-Agent", value: "Vodia-PBX/69.5.3" }
]);
// WebSocket event handlers
ws.on('open', function() {
console.log("Websocket opened");
});
ws.on('close', function() {
console.log("Websocket closed");
call.stream();
});
// Handle incoming messages
ws.on('message', function(message) {
if (message.type != "response.audio.delta") {
console.log(message);
}
var msg = JSON.parse(message);
if (msg.type == "session.created") {
console.log("Session created, sending update with instructions");
// Initial session configuration
var update = {
type: "session.update",
session: {
instructions: "Ensure your OpenAI instructions are designed to capture specific information in the transcript. For example, to initiate a call transfer to extension 500, the transcript should explicitly include "transfer: 500". Similarly, guest details such as "guest_name: chris", "guest_count: 5", guest_countrty: USA, "start_date: 30/05/2025" and "last_date: 15/06/2025" must be present in the output for proper processing",
turn_detection: {
type: "server_vad",
threshold: 0.5,
prefix_padding_ms: 300,
silence_duration_ms: 500
},
voice: "echo", // For a male English accent
temperature: 0.7,
max_response_output_tokens: 1024,
tools: [],
modalities: ["text","audio"],
input_audio_format: codec,
output_audio_format: codec,
input_audio_transcription:{ model: "whisper-1" },
tool_choice: "auto"
}
};
ws.send(JSON.stringify(update));
}
else if (msg.type == "session.updated") {
// Start streaming to capture caller audio
call.stream({
codec: codec,
interval: 0.5,
callback: stream
});
// Send the initial greeting
if (!greeted) {
console.log("Sending initial greeting command");
greeted = true;
// Create a conversation item with the proper structure
var greetingInput = {
"type": "conversation.item.create",
"item": {
"role": "system",
"type": "message",
"content": [
{
"type": "input_text",
"text": "The call has connected. Greet the caller immediately. Detect the caller's language and respond in that language. If speaking English, say: 'Thank you for calling Vodia Hotel. This is Scott speaking. How may I assist you today?' Use a refined, male English accent. For other languages, provide an equivalent greeting while maintaining Scott's professional, sophisticated demeanor."
}
]
}
};
ws.send(JSON.stringify(greetingInput));
// Request a response after adding the conversation item
setTimeout(function() {
var responseRequest = {
"type": "response.create"
};
ws.send(JSON.stringify(responseRequest));
}, 500);
}
}
else if (msg.type == "response.audio.delta") {
var audio = fromBase64String(msg.delta);
call.play({
direction: "out",
codec: codec,
audio: audio
});
}
else if (msg.type == "response.audio.done") {
console.log("Audio playback completed");
// If we have a pending transfer, now it's safe to execute it
if (pendingTransfer && !transferComplete) {
console.log("Audio finished, now safe to execute transfer to extension: " + pendingTransfer);
executeTransfer();
}
}
else if (msg.type == "response.audio_transcript.done") {
if (msg.transcript) {
console.log("Message transcript: " + msg.transcript);
// Extract all information from transcript
// Extract date information if present - using regex patterns
if (msg.transcript.indexOf("start_date:") !== -1) {
var startDateMatch = msg.transcript.match(/start_date:\s*(\d{1,2}\/\d{1,2}\/\d{4})/i);
if (startDateMatch && startDateMatch[1]) {
var start_date = startDateMatch[1].trim();
console.log("Extracted start_date: " + start_date);
pendingStartDate = start_date;
} else {
console.log("Failed to extract start_date with pattern");
}
}
if (msg.transcript.indexOf("Last date:") !== -1) {
var lastDateMatch = msg.transcript.match(/Last date:\s*(\d{1,2}\/\d{1,2}\/\d{4})/i);
if (lastDateMatch && lastDateMatch[1]) {
var last_date = lastDateMatch[1].trim();
console.log("Extracted last_date: " + last_date);
pendingLastDate = last_date;
} else {
console.log("Failed to extract last_date with pattern");
}
}
// Extract guest name
if (msg.transcript.indexOf("guest_name:") !== -1) {
var nameMatch = msg.transcript.match(/guest_name:\s*([^,\.]+)/i);
if (nameMatch && nameMatch[1]) {
var guest_name = nameMatch[1].trim();
console.log("Extracted guest_name: " + guest_name);
pendingGuestName = guest_name;
} else {
console.log("Failed to extract guest_name with pattern");
}
}
// Extract guest country
if (msg.transcript.indexOf("guest_country:") !== -1) {
var countryMatch = msg.transcript.match(/guest_country:\s*([^,\.]+)/i);
if (countryMatch && countryMatch[1]) {
var guest_country = countryMatch[1].trim();
console.log("Extracted guest_country: " + guest_country);
pendingGuestCountry = guest_country;
} else {
console.log("Failed to extract guest_country with pattern");
}
}
// Extract guest count
if (msg.transcript.indexOf("guest_count:") !== -1) {
var countMatch = msg.transcript.match(/guest_count:\s*(\d+)/i);
if (countMatch && countMatch[1]) {
var guest_count = countMatch[1].trim();
console.log("Extracted guest_count: " + guest_count);
pendingGuestCount = guest_count;
} else {
console.log("Failed to extract guest_count with pattern");
}
}
// Check if transcript contains the transfer command and store it for later
if (!transferComplete && msg.transcript.indexOf("transfer: ") !== -1) {
// Extract the transfer code - simple split rather than regex
var parts = msg.transcript.split("transfer: ");
if (parts.length > 1) {
var extension = parts[1].trim();
console.log("Found transfer code: " + extension + " (will execute after a delay to allow audio to complete)");
// Store for later execution
pendingTransfer = extension;
// No immediate action - we'll wait for the audio to finish
} else {
console.log("Transfer command format incorrect: " + msg.transcript);
}
}
}
}
else if (msg.type == "response.done") {
// Mark that the full response is complete
responseComplete = true;
console.log("Response completed, checking for pending transfer");
// If we have a pending transfer, we'll schedule it to execute after audio completes
if (pendingTransfer && !transferComplete) {
console.log("Transfer ready, waiting for audio playback to finish");
// As a safety fallback, queue a delayed transfer in case audio.done never fires
setTimeout(function() {
if (!transferComplete && pendingTransfer) {
console.log("FALLBACK: Audio.done event may not have fired, executing delayed transfer to extension: " + pendingTransfer);
executeTransfer();
}
}, 15000);
}
}
});
// Function to stream audio to OpenAI
function stream(audio) {
var frame = JSON.stringify({
"type": "input_audio_buffer.append",
"audio": toBase64String(audio)
});
ws.send(frame);
}
// Connect the WebSocket
ws.connect();
The external application will receive the following data. (Example data follows.)
{"startdate": "15/05/2025", "enddate": "30/05/2025", "name": "Christo Da Silva", "country": "Australia", "guests": "5", "callernumber": "test <sip:504@sbc.vodia-teams.com>", "callednumber": "open ai -hotel <sip:101@sbc.vodia-teams.com>"}
tip
Use 'Connection': 'keep-alive'
header in your response.
For more information on Vodia's JavaScript capabilities, refer to: Vodia Backend JavaScript Documentation