Skip to main content

IVR JS Example (Customer verification)

IVR JS Example - Customer verification

Demonstrating Vodia's Javascript IVR capabilities, this example script showcases advanced call handling techniques for secure banking verification, combining DTMF input, OpenAI voice integration, and external system communication to create a robust customer verification flow.

Scenario:

  • Multi-Factor Authentication: Securely collecting credit card numbers and date of birth via DTMF, then personal details via voice.
  • OpenAI Integration: Providing natural language conversational capabilities for collecting customer information.
  • Personal Information Collection: Gathering details such as first name, last name, and address through conversational AI.
  • External System Integration: Transmitting collected customer data in JSON format to an external verification system. The external system uses this data to send an OTP to the customer's mobile phone.
  • OTP Verification Flow: The PBX gathers the OTP and transmits it to the external system for verification.
  • Intelligent Call Routing: Transferring verified customers to appropriate departments based on verification results.
tip

Please tailor these example OpenAI instruction prompts to meet your specific requirements and ensure all necessary security measures are implemented and tested.

Implementation Details

The script follows a clear sequential flow:

  • Initial Customer Greeting: TTS welcomes customer and requests credit card information
  • DTMF Collection Phase: Collects credit card and DOB through touch-tone input(DTMF)
  • OpenAI Conversation Phase: Connects to OpenAI to collect name and address
  • Primary Verification: Sends all collected information to external verification system
  • OTP Verification (if required): Collects one-time password and send it to the external system for verification.
  • Call Completion: Transfers to appropriate department based on verification results
//
// Bank Customer Verification IVR with OpenAI integration
//
// This script handles customer verification through:
// 1. DTMF capture for credit card number and DOB
// 2. Voice interaction for address and name collection
// 3. External validation with OTP verification
//
'use strict';


// Customer information storage
var creditCardNumber = '';
var dateOfBirth = '';
var firstName = '';
var lastName = '';
var address = '';
var otpCode = '';
var to = '';
var from = '';
var secret = '';
var cleanFirstName = '';
var cleanLastName = '';
var cleanAddress = '';

// API key for OpenAI
var secret = "openai-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 creditCardCollected = false;
var dobCollected = false;
var personalInfoCollected = false;
var verificationSent = false;
var otpRequested = false;
var otpVerified = false;
var transferComplete = false;

// 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 verification data to webhook
function sendVerificationData() {
console.log("WEBHOOK: Preparing to send verification data");


// Clean up data before sending
var cleanFirstName = firstName ? firstName.replace(/last_name.*$/i, '').trim() : '';
var cleanLastName = lastName ? lastName.replace(/address.*$/i, '').trim() : '';
var cleanAddress = address ? address.replace(/verification_complete.*$/i, '').trim() : '';

var body = JSON.stringify({
creditCardNumber: creditCardNumber,
dateOfBirth: dateOfBirth,
firstName: cleanFirstName,
lastName: cleanLastName,
address: cleanAddress,
callernumber: from,
callednumber: to
});
console.log("WEBHOOK: Request body: " + body);

var args = {
method: 'POST',
url: 'http://external_server/webhook',
header: [ { name: 'Content-Type', value: 'application/json' } ],
callback: handleVerificationResponse
};

if (body) {
args.body = body;
}

console.log("WEBHOOK: Sending HTTP request to webhook");
verificationSent = true;
try {
system.http(args);
console.log("WEBHOOK: HTTP request sent successfully");
} catch (e) {
console.log("WEBHOOK ERROR: " + e.message);
handleFailure();
}
}

// Function to send OTP verification to webhook
function sendOtpVerification() {
console.log("WEBHOOK: Preparing to send OTP verification");

var body = JSON.stringify({
creditCardNumber: creditCardNumber,
otpCode: otpCode,
callernumber: from,
callednumber: to
});
console.log("WEBHOOK: Request body: " + body);

var args = {
method: 'POST',
url: 'http://external_server/verifyotp',
header: [ { name: 'Content-Type', value: 'application/json' } ],
callback: handleOtpResponse
};

if (body) {
args.body = body;
}

console.log("WEBHOOK: Sending OTP verification request");
try {
system.http(args);
console.log("WEBHOOK: OTP verification request sent successfully");
} catch (e) {
console.log("WEBHOOK ERROR: " + e.message);
handleFailure();
}
}

// Callback function to handle verification response
function handleVerificationResponse(code, response, headers) {
console.log("Verification response code: " + code);
console.log("Verification response: " + response.toString());

try {
var responseData = JSON.parse(response);

// Play message if provided
if (responseData.message) {
call.say(responseData.message);
console.log("Playing message: " + responseData.message);
}

// Handle various verification outcomes
if (responseData.otp_required) {
// Request OTP from customer
otpRequested = true;
setTimeout(function() {
call.say("Please enter your 6-digit verification PIN.");
otpCode = '';
call.dtmf(onOtpDtmf);
}, 1000);
} else if (responseData.transfer) {
// Handle direct transfer
var destination = responseData.destination || "700";
setTimeout(function() {
executeTransfer(destination);
}, 3000);
} else {
// Default fallback
handleFailure();
}
} catch (e) {
console.log("Error parsing verification response: " + e.message);
handleFailure();
}
}

// Callback function to handle OTP verification response
function handleOtpResponse(code, response, headers) {
console.log("OTP verification response code: " + code);
console.log("OTP verification response: " + response.toString());

try {
var responseData = JSON.parse(response);

// Play message if provided
if (responseData.message) {
call.say(responseData.message);
console.log("Playing message: " + responseData.message);
}

// Handle OTP verification result
if (responseData.otp_verified) {
otpVerified = true;
setTimeout(function() {
call.say("Your identity has been successfully verified. Thank you for using our service.");
executeTransfer("700");
}, 3000);
} else if (responseData.retry_otp) {
// Allow customer to retry OTP
setTimeout(function() {
call.say("The verification code is incorrect. Please enter your 6-digit verification PIN again.");
otpCode = '';
call.dtmf(onOtpDtmf);
}, 2000);
} else if (responseData.transfer) {
// Handle direct transfer
var destination = responseData.destination || "705";
setTimeout(function() {
executeTransfer(destination);
}, 3000);
} else {
// Default fallback for failed verification
setTimeout(function() {
call.say("Verification failed. Transferring you to a customer service representative.");
executeTransfer("705");
}, 2000);
}
} catch (e) {
console.log("Error parsing OTP verification response: " + e.message);
handleFailure();
}
}

// Helper function for failures
function handleFailure() {
setTimeout(function() {
call.say("We're experiencing technical difficulties. Transferring you to customer service.");
executeTransfer("705");
}, 2000);
}

// Function to execute transfer
function executeTransfer(destination) {
if (transferComplete) return;

// Log all collected information
console.log("Credit Card Number: " + creditCardNumber);
console.log("Date of Birth: " + dateOfBirth);
console.log("Name: " + firstName + " " + lastName);
console.log("Address: " + address);
if (otpCode) console.log("OTP Code: " + otpCode);
console.log("OTP Verified: " + otpVerified);

// 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 delay to ensure any in-progress audio completes
setTimeout(function() {
// Execute the transfer
call.mute();
try {
console.log("Transferring call to extension: " + destination);
call.transfer(destination);
console.log("Transfer command executed");
} catch (e) {
console.log("Transfer error: " + e.message);
}
}, 500);
}

// DTMF handler for credit card number
function onCreditCardDtmf(digit) {
creditCardNumber += digit;
console.log("Credit card input: " + creditCardNumber.length + " digits");

// Assuming credit card is 16 digits
if (creditCardNumber.length === 16) {
console.log("Credit card collection complete: " + creditCardNumber);
creditCardCollected = true;
call.say("Thank you. Now, please enter your date of birth in format DD MM YYYY For example, 12031994 for 12th March 1994.");
dateOfBirth = '';
call.dtmf(onDobDtmf);
}
}

// DTMF handler for date of birth
function onDobDtmf(digit) {
dateOfBirth += digit;
console.log("Date of birth input: " + dateOfBirth.length + " digits");

// Date of birth should be 8 digits (DDMMYYYY)
if (dateOfBirth.length === 8) {
console.log("Date of birth collection complete: " + dateOfBirth);
dobCollected = true;

// Now initiate OpenAI for voice collection of name and address
initializeOpenAI();
}
}

// DTMF handler for OTP code
function onOtpDtmf(digit) {
otpCode += digit;
console.log("OTP input: " + otpCode.length + " digits");

// OTP should be 6 digits
if (otpCode.length === 6) {
console.log("OTP collection complete: " + otpCode);
sendOtpVerification();
}
}

// Open the websocket connection to OpenAI
function initializeOpenAI() {
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("OpenAI websocket opened");
});

ws.on('close', function() {
console.log("OpenAI websocket closed");
call.stream();

// If we have all the necessary information, send it for verification
if (firstName && lastName && address && !verificationSent) {
personalInfoCollected = true;
sendVerificationData();
} else if (!personalInfoCollected) {
// Fallback if we didn't collect all information
handleFailure();
}
});

// Handle incoming messages from OpenAI
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: "You are a bank verification assistant. Your job is to politely collect the customer's first name, last name, and address. Ask for these pieces of information one at a time, and confirm each answer before moving on. After collecting all information, tell the customer you'll proceed with verification. IMPORTANT: At the end of the conversation, you must output 'first_name: [FirstName]', 'last_name: [LastName]', and 'address: [FullAddress]' so the system can extract this information. Do not ask for any other personal information, especially not credit card or financial details. Keep responses concise and professional. Always end with 'verification_complete: true' when you have all needed information.",
turn_detection: {
type: "server_vad",
threshold: 0.3,
prefix_padding_ms: 500,
silence_duration_ms: 2500
},
voice: "alloy",
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: function(audio) {
var frame = JSON.stringify({
"type": "input_audio_buffer.append",
"audio": toBase64String(audio)
});
ws.send(frame);
}
});

// Send the initial message to start collection
console.log("Sending initial greeting command");

// Create a conversation item with the proper structure
var startMessage = {
"type": "conversation.item.create",
"item": {
"role": "system",
"type": "message",
"content": [
{
"type": "input_text",
"text": "The call has connected. The customer has already provided their credit card number and date of birth via touch tone. Now you need to collect their first name, last name, and address. Start with 'Thank you for the information. For verification purposes, may I please have your first name?'"
}
]
}
};

ws.send(JSON.stringify(startMessage));

// 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_transcript.done") {
if (msg.transcript) {
console.log("Message transcript: " + msg.transcript);

// Extract information from transcript
if (msg.transcript.indexOf("first_name:") !== -1) {
var nameMatch = msg.transcript.match(/first_name:\s*([^\n]+)/i);
if (nameMatch && nameMatch[1]) {
firstName = nameMatch[1].trim();
console.log("Extracted first name: " + firstName);
}
}

if (msg.transcript.indexOf("last_name:") !== -1) {
var lastNameMatch = msg.transcript.match(/last_name:\s*([^\n]+)/i);
if (lastNameMatch && lastNameMatch[1]) {
lastName = lastNameMatch[1].trim();
console.log("Extracted last name: " + lastName);
}
}

if (msg.transcript.indexOf("address:") !== -1) {
var addressMatch = msg.transcript.match(/address:\s*([^\n]+(?:\n[^v][^\n]*)*)/i);
if (addressMatch && addressMatch[1]) {
address = addressMatch[1].trim();
console.log("Extracted address: " + address);
}
}

// Check if verification is complete
if (msg.transcript.indexOf("verification_complete: true") !== -1) {
console.log("Verification information collection complete");
personalInfoCollected = true;

// Check if we have all required information
if (firstName && lastName && address) {
console.log("All required information collected, proceeding with verification");

// Prepare to close the websocket which will trigger verification
setTimeout(function() {
ws.close();
}, 2500);
}
}
}
}
});

// Connect the WebSocket
ws.connect();
}

// Start the call flow
console.log("Starting bank verification IVR");
call.say('Welcome to our secure banking service. For verification purposes, please enter your 16-digit credit card number.');
creditCardNumber = '';
call.dtmf(onCreditCardDtmf);

The external application will receive the following data. (Example data follows)

// Data that need to be verfied
{"creditCardNumber": "8745632112345687", "dateOfBirth": "723101985", "firstName": "John", "lastName": "Smith", "address": "126 Guildford Road, Maylands", "callernumber": "test <sip:504@sbc.vodia-teams.com>", "callednumber": "open ai -hotel <sip:101@sbc.vodia-teams.com>"}

// OTP entered by the caller
{"creditCardNumber": "1452368778456321", "otpCode": "785412", "callernumber": "test <sip:504@sbc.vodia-teams.com>", "callednumber": "open ai -hotel <sip:101@sbc.vodia-teams.com>"}

The external application sends the following example JSON responses.

// Standard verification response
{
"destination": "785", "message": "Thanks Mr. Jones, please enter your 6-digit PIN now", "otp_required": "true"
}

// OTP verification response
{
"message": "PIN verified successfully", "otp_verified": "true", "destination": "700", "transfer": "true"
}

// Failed verification
{
"message": "Verification failed", "destination": "705", "transfer": "true"
}

// Retry OTP
{
"message": "Incorrect PIN, please try again", "retry_otp": "true"
}