Registering and making callbacks with a CFD app

Introduction

In this guide we will show how to combine a callflow and a dialer in the same 3CX Call Flow Designer project, to create an application that registers and makes callbacks. We will use speech recognition to ask for the date and time of the callback, text-to-speech to play back the recognized text, and we will show how to pass this information from the callflow to the dialer.

💡 Tip: The project for this example application is available via the CFD Demos GitHub page, and is installed along with the 3CX Call Flow Designer in your Windows user documents folder, i.e. “C:\Users\YourUsername\Documents\3CX Call Flow Designer Demos”.

This application could be used as the “Destination if no answer” for a Queue. This way, when customers can’t get connected to an agent, they can interact with this application to schedule a callback at the time of their convenience. Then, the dialer included in the application will make the call when the time comes.

Step 1: Create the Project

First, we need to create a new project. Open the CFD and go to “File” > “New” > “Callflow Project”, select the folder where you want to save it, and enter a name for the project, e.g. Callback.

Step 2: Configure online services

When asking for the date and time in which the customer would like to be called back, we will use speech recognition. Therefore we need to configure online services. Follow this guide to configure Text to Speech and Speech to Text with Google Cloud.

Step 3: Add the Menu component to offer the callback

The first thing the app needs to do when a call is received, is to check if the caller wants to configure a callback or not. To do this, we will use a Menu component with 2 options:

  1. Drag a “Menu” component from the toolbox, and drop it into the design surface of the “Main” callflow. Then select the component added, go to the “Properties Window” and rename it to “OfferCallbackMenu”.

Offer callback callflow menu

  1. Double-click on the “Menu” component to configure these properties:
  • “Initial Prompts” - configure a Text to Speech prompt with the text "To schedule a callback press 1, otherwise press 2.".
  • “Subsequent Prompts” - configure a Text to Speech prompt with the text "To schedule a callback press 1, otherwise press 2.".
  • “Timeout Prompts” - configure a Text to Speech prompt with the text "Sorry, we didn't receive any digit.".
  • “Invalid Digit Prompts” - configure a Text to Speech prompt with the text "Sorry, the selected option is not valid.".
  • Configure the remaining options as in the above example.
  1. Press “OK” to save the changes.

Offer call back menu options

  1. For Option 2 and the “Timeout or Invalid Option” branches we will just play a goodbye prompt with the text "Thanks for contacting us, good bye.". We can use the Prompt Playback component and a Text to Speech prompt to do this.
  2. For Option 1 we need to ask for a date, a time, and finally store this information in 3CX, so the dialer can make the call at the specified date and time. We will use 3 User Defined components to implement each of these 3 tasks.

Step 4: Creating a component to ask for the callback date

The logic to ask for the callback date will be implemented in a user defined component. To do this, we will use speech recognition, so the user can freely speak the date. Let’s get started:

  1. In the Project Explorer window, right click the project node and select “New Component”. Set the name of the component to “AskForDate.comp”. Double click the item just created to open it in the designer.

Ask for a date component

  1. For speech recognition we need to use the Voice Input component. And once we have the recognized text, we will try to convert it to a valid date using a C# script. As this conversion might fail because the recognized speech might not be a valid date, we will surround the recognition logic with a Loop component. This way, we can repeat the procedure as long as we need, and retry the speech recognition until we get a valid date. As a result, we will start by adding a Loop component, and naming it “AskDateLoop”.

AskDateLoop component

  1. For the Loop condition we will need a boolean variable initially set to true, and we will keep looping while this variable is true. Let’s create a private variable by selecting the component in the Project Explorer, then clicking the button on the right of the Variables item in the Properties window, and finally adding the variable in the Variable Collection Editor. Then assign this variable to the Condition property of the Loop component.
  2. Now let's add the Voice Input component inside the Loop component, and name it “RequestDate”. Configure it as follows:
  • “Initial Prompts” - configure a Text to Speech prompt with the text "Please, say for what date you want to schedule the callback.".
  • “Subsequent Prompts” - configure a Text to Speech prompt with the text "Please, say for what date you want to schedule the callback.".
  • “Timeout Prompts” - configure a Text to Speech prompt with the text "Sorry, we couldn't hear you.".
  • “Invalid Input Prompts” - configure a Text to Speech prompt with the text "Sorry, we couldn't understand what you said.".
  • Let’s configure 4 hints, which will tell the Speech Recognition engine what to expect from the voice input:
  • $MONTH $DAY
  • $DAY of $MONTH
  • today
  • tomorrow

AskDateLoop component input options

  1. For the “Invalid Input” branch we will just play a prompt with the text "Sorry, we couldn't schedule the callback, please call again later.", and then disconnect the call.
  2. For the “Valid Input” branch, we need to use a C# script to parse the recognized text and try to match it to a valid date, or check if the recognized text has the word “today” or “tomorrow” inside. We can use the following C# code for this, which returns true when the recognized text is a valid date, and false otherwise:

string inputLowerCase = input.ToLower();

if (inputLowerCase.Contains("today") || inputLowerCase.Contains("tomorrow"))

    return true;

string[] validformats = new[]

{

    "MMMM d",

    "MMMM d\\s\\t",

    "MMMM dn\\d",

    "MMMM dr\\d",

    "MMMM d\\t\\h",

    "d o\\f MMMM",

    "d\\s\\t o\\f MMMM",

    "dn\\d o\\f MMMM",

    "dr\\d o\\f MMMM",

    "d\\t\\h o\\f MMMM"

};

return DateTime.TryParseExact(inputLowerCase, validformats, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out _);

C# code execution

  1. Now we need to check the C# script outcome. We use the Create a Condition component for this. The condition will have 2 branches, the first will be named “ValidDate” and the second “InvalidDate”. The condition for the “ValidDate” branch will be
    IsValidDate.ReturnValue
    .

AskDateLoop structuring

  1. In the “InvalidDate” branch we just need to play an error message, for example "Sorry, we couldn't understand the date you said. Please try again.". This way the Loop will start the speech recognition again, and the caller will have another chance to say the date.
  2. In the “ValidDate” branch we need to use another C# script to get the actual date from the recognized text (the previous C# script just validated the date, but didn’t return the actual value). This C# Script is pretty similar, but needs to consider that when the user says “today” we need to return the current date, and if the user says “tomorrow” we need to add 1 day to the current date. Otherwise we just use the standard date conversion methods to convert the string to a DateTime object. The following C# script will do the work:

DateTime nowDt = DateTime.Now;

DateTime todayDt = new DateTime(nowDt.Year, nowDt.Month, nowDt.Day, 0, 0, 0);

string inputLowerCase = input.ToLower();

if (inputLowerCase.Contains("today")) return todayDt;

if (inputLowerCase.Contains("tomorrow")) return todayDt.AddDays(1);

string[] validformats = new[]

{

    "MMMM d",

    "MMMM d\\s\\t",

    "MMMM dn\\d",

    "MMMM dr\\d",

    "MMMM d\\t\\h",

    "d o\\f MMMM",

    "d\\s\\t o\\f MMMM",

    "dn\\d o\\f MMMM",

    "dr\\d o\\f MMMM",

    "d\\t\\h o\\f MMMM"

};

DateTime dt = DateTime.ParseExact(inputLowerCase, validformats, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None);

return dt < todayDt ? dt.AddYears(1) : dt;

Convert to date

  1. The final steps for this user defined component will be assigning the selected date to a variable (so it can be read by the callflow invoking this component), playing the selected date to the caller, and changing the value of the variable HasToAskDate to false, so the Loop exits and the call can continue the execution in the main callflow. The “Valid Input” branch of the Voice Input component will look like the following:

<b>“Valid Input”</b> branch of the Voice Input component

Step 5: Creating a component to ask for the callback time

We will create a user defined component to ask for the callback time as well, in a very similar way than what we did for the date. We will include the Loop, the Voice Input component, and the C# scripts to validate the time. Proceed as follows:

  1. In the Project Explorer window, right click the project node and select “New Component”. Set the name of the component to “AskForTime.comp”. Double click the item just created to open it in the designer.

Ask for the callback time

  1. Create a private variable named “HasToAskTime”, and 2 public variables: “In_SelectedDate” to receive the date recognized in the previous step, and “Out_SelectedDateTime” to return a DateTime object containing both the date and time.

AskTimeLoop

  1. Add a Loop component, set the Condition property to the variable
    callflow$.HasToAskTime
    , and then add a Voice Input component inside. Configure it as follows:
  • “Initial Prompts” - configure a Text to Speech prompt with the text "Please, say for what time you want to schedule your callback.".
  • “Subsequent Prompts” - configure a Text to Speech prompt with the text "Please, say for what time you want to schedule your callback.".
  • “Timeout Prompts” - configure a Text to Speech prompt with the text "Sorry, we couldn't hear you.".
  • “Invalid Input Prompts” - configure a Text to Speech prompt with the text "Sorry, we couldn't understand what you said.".
  • For the hints, we just need to tell the Speech Recognition engine what to expect a time, so we use the following speech adaptation token:
  • $TIME

Invalid input

  1. For the “Invalid Input” branch of the Voice Input component, we will just play an error prompt, for example "Sorry, we couldn't schedule the callback, please call again later.", and then disconnect the call.
  2. For the “Valid Input” branch, we need to use a C# script to parse the recognized text and try to match it to a valid time. To do this validation, we will include the date selected in the previous step, and concatenate it with the recognized text for the time, this way we can do the validation of a complete date & time object. We can use the following C# code for this, which returns true when the recognized text is a valid time, and false otherwise:

string dateStr = ((DateTime)selectedDate).ToString("yyyy-MM-dd");

string[] validformats = new[]

{

    "yyyy-MM-dd H:mm",

    "yyyy-MM-dd H",

    "yyyy-MM-dd h:mm tt",

    "yyyy-MM-dd h tt"

};

return DateTime.TryParseExact(dateStr + " " + input.Replace(".", ""), validformats, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out _);

C# script to parse the recognized text and try to match it to a valid time

  1. Next, we need to use the Create a Condition component to check the script execution result, which will have 2 branches: “ValidTime” and “InvalidTime”. This is pretty similar to what we did for the date recognition, so we will not go into the details (you can get the final CFD project from Github or the demo projects folder included when you install 3CX). The “Valid Input” branch of the Voice Input component will look like this:

Valid input component

Step 6: Creating a component to store the details in 3CX

Now that the user has selected the date and time for the callback, we need to store somewhere this information, so the dialer can read it later and make the call when the time comes. For the sake of simplicity, we will use a 3CX Global Property for this, however this has some limitations that you should be aware of, and might not be the best approach for a productive application. Storing the information in a database for example would be a better approach for this.

To store the callback details in a 3CX Global Property, we need to do the following:

  1. Format the selected date and time as a string, with format “yyyyMMddHHmmss”.
  2. Get the current value of the 3CX Global Property, so we can add the new callback while keeping the previously configured callbacks.
  3. Append the new callback details to the property value, and store it back to 3CX. The format of this parameter will be:

caller_number_1=date_time_1,caller_number_2=date_time_2,caller_number_3=date_time_3

As you can see, this could lead to problems when dealing with concurrent calls, because 2 different calls could read the same 3CX Global Property value, then edit it, the first call adds its callback, and when the second call will do the same, the data of the callback for the first call will be overwritten. This would not happen if we use a database for example, in which we can just add new records to a table.

Said this, let’s proceed with the steps that need to be performed in order to store the callback details in a 3CX Global Property:

  1. Create a new user defined component and name it “StoreCallback.comp”.

Variable collection editor

  1. Add a public variable named “In_SelectedDateTime” which will be used to receive the selected date and time.
  2. We can use the following C# script to format the DateTime object as a string:

return ((DateTime)datetime).ToString("yyyyMMddHHmmss");

Selected date and time format

  1. Add a 3CX Get Global Property component to read the value of the custom property "CFD_SCHEDULED_CALLBACKS".

Create a condition component for empty parameters

  1. We need to add the callback information to this global property. Something to keep in mind is that, when the value is empty, we just need to add the callback details, but when the value is not empty, we also need to add a comma character at the beginning, as a separator. So, we will use a Create a condition component for this, and append the appropriate value depending on the case. Next, we will use the 3CX Set Global Property component to update the property value.
  2. Finally, add a Prompt Playback component, so we can play a confirmation message, for example "Your callback has been successfully scheduled. We will contact you back shortly. Thank you.". The component should like like the following:

Add a Prompt Playback component

Step 7: Invoking the user defined components from the main flow

Now that we have our 3 user defined components, we just need to add them to our main callflow, inside the Option 1 branch of the “OfferCallbackMenu”:

  1. Drag the “AskForDate” component from the toolbox to the designer. Change the name to “RequestDate”.

Invoking the user defined components from the main flow

  1. Drag the “AskForTime” component from the toolbox to the designer, under the “RequestDate” component. Change the name to “RequestTime”. Configure the property “In_SelectedDate” to the output property
    RequestDate.Out_SelectedDate
    .

RequestTime component to call back

  1. Drag the “StoreCallback” component from the toolbox to the designer, under the “RequestTime” component. Change the name to “StoreCallbackIn3CX”. Configure the property “In_SelectedDateTime” to the output property
    RequestTime.Out_SelectedDateTime
    .

Step 8: Creating the dialer to start the callback

The callflow is ready, and we can make calls to it and check that the global property is created, and the callback information is added to it. The final step is creating the dialer to start these callbacks. Let’s proceed:

  1. In the Project Explorer window, right click the project node and select “New Dialer”. Set the name of the dialer to “CallbackDialer.dialer”. Double click the item just created to open it in the designer.
  2. In this demo we will use a Power Dialer, which will check for callbacks to be made at a steady pace. It’s also possible to configure the dialer as a Predictive Dialer, and then ensure that an agent will be available when the call is made, however this might mean that the call is not started exactly at the time the user has requested it. Consider checking this page from the manual on how dialers can be configured.
  3. The first thing we need to do when the dialer flow is executed, is reading the value of the global property "CFD_SCHEDULED_CALLBACKS". We will use the 3CX Get Global Property component for this.
  4. Next, we need to parse the value read and check if there is a callback to be made. For this, we will check for any callback requested for a date and time equal or before the current date and time. We can use the following C# script for this, which returns the number to call, or an empty string when no callback should be made:

string[] callbackArray = callbacks.Split(',');

foreach (string callback in callbackArray)

{

    string[] callbackParts = callback.Split('=');

    if (callbackParts.Length == 2)

    {

        string number = callbackParts[0];

        DateTime dt = DateTime.ParseExact(callbackParts[1], "yyyyMMddHHmmss", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None);

        if (dt <= DateTime.Now) return number;

    }

}

return "";

Execute #C Code - Get Number to Call

  1. Now we need to check the result of the C# script execution. If we have a value different from an empty string, that means that we need to call that number. We can use a Create a Condition component with a single branch. The Condition for this branch should be set to the following expression:

GREAT_THAN(LEN(GetNumberToCall.ReturnValue),0)

Callback dialer component

  1. Now that we know that we have a number to call, we need to remove it from the global property and make the call. To remove it, we will use the following C# script:

string[] callbackArray = callbacks.Split(',');

foreach (string callback in callbackArray)

{

    string[] callbackParts = callback.Split('=');

    if (callbackParts.Length == 2)

    {

        if (number == callbackParts[0])

        {

            DateTime dt = DateTime.ParseExact(callbackParts[1], "yyyyMMddHHmmss", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None);

            if (dt <= DateTime.Now)

            {

                return callbacks.Replace(callback, "").Replace(",,",",");

            }

        }

    }

}

return callbacks;

Execute #C Code - Remove number to call

  1. The previous C# script returns the updated value that we should set to the global property. So we use a 3CX Set Global Property component to perform this update.

Make call back component

  1. Finally, we make the call to the returned number, and connect it internally to the extension 800, which in our 3CX installation is the queue that will handle this call.
  2. The complete dialer should look like this:

Callback dialer component

Step 9: Build and Deploy to 3CX Phone System

The project is ready to build and upload to our 3CX Phone System server, with these steps:

  1. Select “Build” > “Build All” and the CFD generates the file “Callback.zip”.
  2. Go to the “3CX Management Console” > “Advanced” > “Call Flow Apps” > “Add/Update”, and upload the ZIP file generated by the CFD in the previous step.
  3. The Call Flow app is ready to use. Make a call to the application, register a callback and wait for the call to be made.

See Also

Last Updated
This document was last updated on 29th April 2021
https://www.3cx.com/docs/cfd-callback/