Skip to main content

Cold Transfer (Unattended Transfer)

Call Diagrams and Workflows

Each of the transfer types above is illustrated in the below diagrams. The diagrams illustrate what action and/or API should be used at each setup, and how.

Description

This scenario is based upon a single Cloudonix API request, called switch voice application. Upon issuing the request, the voice agent will automatically disconnect from the call with the caller, and the caller will be redirected to the human agent, using a simple CXML document.

Cold Transfer Diagram

Workflow Outline

  1. An inbound call is accepted at Cloudonix for the phone number +12127773456.
  2. The call is routed to ReTell using the Register Phone Call API request.
  3. The voice agent issues a switch voice application request to Cloudonix, using the agent destination number and a well-formatted CXML document.
  4. Cloudonix removes the voice agent from the call and disconnects it, following the completion of switch voice application.

Workflow Implementation

To implement this workflow, you would need to:

  1. Create a Container Application to process the inbound call to Cloudonix and respond with a static CXML document.
  2. Create a Voice Application to execute the Container Application mentioned in step 1, based upon the inbound call phone number.
  3. Create a ReTell function, to initiate the transfer, based upon a static CXML application that will be included in the function call.

Step 1: Inbound Calls Processing Container Application

Use the script in the following example, to route calls from the world to your Retell voice agent, via Cloudonix.

Retellai: Pre-Call Webhook Routing

note

We will assume that your container application is named smartTransfers and that the relevant container application block is named staticRoutingToReTell.

Sample smartTransfers container application code
const httpClient = require('https');

/* ReTell API Config */
const retell_token = '{{ put_your_retell_api_token_here }}';
let retell_agent = '{{ put_your_default_retell_agent_id_here}}';

/* This function provides a simple way to manage multiple inbound phone numbers routing */
function selectAgentIdByNumber(ev) {
switch (ev.parameters.To) {
case "+12127773456":
case "12127773456":
retell_agent='your_agent_id_here';
break;
default:
retell_agent='you_default_agent_id_here';
break;
}
}

/* Handle HTTP outbound requests to Retell and other APIs */
async function httpsRequest(url, method = 'GET', headers = {}, body = null, requestTimeout = 5000) {

const start = Date.now();

let response;

if (method === 'POST') {
response = await httpClient.post(url, { headers, body });
} else if (method === 'PUT') {
response = await httpClient.put(url, { headers, body });
} else if (method === 'DELETE') {
response = await httpClient.delete(url, { headers });
} else if (method === 'GET') {
response = await httpClient.get(url, { headers });
} else {
throw new Error(`Unsupported method: ${method}`);
}

const elapsed = Date.now() - start;
if (elapsed > requestTimeout) {
console.log(`[httpsRequest] HTTP Request ${method} to ${url} timed out after ${elapsed} mSec`);
return false;
}

const status = response.status || response.statusCode || 0;
const ok = status >= 200 && status < 300;
const responseBody = typeof response.body === 'string'
? response.body : JSON.stringify(response.body || {});

return {
status,
ok,
body: responseBody,
headers: response.headers || {}
};
}

/* Register a new phone call on Retell */
async function getRetellCallId(ev) {
const url = 'https://api.retellai.com/v2/register-phone-call';

// SIP headers from Cloudonix
const inboundSipHeaders = JSON.parse(ev.body).SessionData.profile['trunk-sip-headers'];

/* Register the Cloudonix LLM variables, we'll need these later on */
const retellDynamicVariables = {
cloudonix_callerid: ev.parameters.From,
cloudonix_destination: ev.parameters.To,
cloudonix_token: ev.parameters.Session,
cloudonix_domain: ev.parameters.Domain
};

/* Document the inbound SIP headers, these come in handy for debugging purposes */
for (const key in inboundSipHeaders) {
retellDynamicVariables[key] = inboundSipHeaders[key];
}

const payload = {
agent_id: retell_agent,
from_number: ev.parameters.From,
to_number: ev.parameters.To,
retell_llm_dynamic_variables: retellDynamicVariables
};

console.log(`[getRetellCallId] Payload: ${JSON.stringify(payload)}`);

const headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + retell_token
};

try {
const response = await httpsRequest(url, 'POST', headers, JSON.stringify(payload));

if (!response.ok) {
throw new Error(`[getRetellCallId] ReTell call registration failed: ${response.status} - ${response.body}`);
}
const data = JSON.parse(response.body);
console.log(`[getRetellCallId] Success: ${data.call_id}`);
return data.call_id;
} catch (error) {
console.error(`[getRetellCallId] Error:`, error.message || error);
await deleteSession(ev.parameters.Domain, ev.parameters.Session);
return false;
}
}

/* Build a simple Retell dialing CXML document */
function dialToRetell(ev, retellCallId) {
let result = `<Dial callerId="${ev.parameters.From}">\n`;
result += ` <Service provider="retell">${retellCallId}</Service>\n`;
result += `</Dial>`;
return result;
}

/* Build the formatted CXML response */
function response(content) {
let result = `<?xml version="1.0"?>`;
result += `<Response>${content}</Response>`;
return result;
}

/* Container Application Request Handler */
exports.handler = async (ev) => {
selectAgentIdByNumber(ev);
const retellCallId = await getRetellCallId(ev);
if (!retellCallId) {
console.log(`[handler] ReTell call registration failed`);
return response("<Reject reason='rejected' />");
}
const retellCxml = dialToRetell(ev, retellCallId);
return response(retellCxml);
};

Step 2: Voice Application Attachment and Call Routing

Now, let's attach our Container Application to a new Voice Application and define the inbound phone number. From the Cloudonix dashboard, click the Applications menu option, and create a a new Voice Application, using the following parameters:

ParameterValue
Application Nameeg. inbound_call_to_retell
Application Resource TypeContainer Application Resource
Container Application ResourceSelect the previously created smartTransfers container application
Entry BlockSelect the previous created main block of the smartTransfers container application

Once completed, a new line will be added to your voice applications view, containing the following information:

NameApplication Resource IDDNID
inbound_call_to_retellsmartTransfers::main-

Now, click the cog icon on the right side, to edit the voice application settings. Once in the next screen, click the plus button, to create a new DNID (Phone number) to assign to your voice application:

Let's review the available options for the DNID:

OptionDescription
DNIDA string representing the DNID to attach to the application, or a pattern of.
Prefix MatchThe DNID entered is formatted as a Prefix Match showing the start of the DNID only.
Glob Wildcard ExpressionThe DNID entered is formatted as a Global Wildcard Expression, eg: 1212777*.
Asterisk ExpressionThe DNID entered is foramatted as an Asterisk dialplan expression, eg: 1212777XXXX.
Regular ExpressionThe DNID entered is formatted as a Regular Expression.
Read More...

To learn more about container applications, voice applications and phone numbers (DNIDs), click any of the links below:

Step 3: ReTell Function for Transfer Initiation

Unlike ReTell's normal transfer calling function, we need to implement a new transfer function, using a Custom Fucntion. Below is what our customer function will look like:

  • Function Name: cold_transfer
  • Description: Transfer the inbound call to human agent 10000 on the connected PBX
  • Endpoint: https://your.favorite.nocode.platform/cold_transfer
  • Parameters:
{
"type": "object",
"properties": {
"transfer_destination": {
"type": "string",
"description": "Where should the call be transferred to"
}
},
"required": [
"transfer_destination"
]
}

Once your custom function is invoked, it should produce the following HTTP Request to Cloudonix:

  • Endpoint URL: https://api.cloudonix.io/calls/{cloudonix_domain}/sessions/{cloudonix_token}/application
  • Endpoint Method: POST
  • Request Body:
{
"cxml": "<Response><Dial trunks='my_pbx'>{transfer_destination}</Dial></Response>",
"method": "POST"
}
pay attention

The above {cloudonix_domain}, {cloudonix_token}, and {transfer_destination} are llm_variables, these should be pre-populated by your no-code scenario (or server side).

API Requests Used