Cloud Telephony Integration (CTI) refers to integrating a Cloud telephony service with Freshworks products. Cloud telephony is a VoIP service over the cloud that is considered a replacement for conventional business telephone equipment.

For CTI apps, Freshworks CRM has a dedicated placeholder, events, and Interface methods that will work globally on all the pages. This allows our CTI app to make and receive calls from any page in Freshworks CRM.

In this tutorial, we are going to build a CTI app for Freshworks CRM.

Take a sneak peek at the complete app that we are going to build a CTI app with all the aforementioned features.

alt_text

Clone this application repository to get started with this tutorial,

git clone https://github.com/freshworks-developers/cti-tutorial-freshworks-crm.git

We are going to build a CTI app through this tutorial. Take a look at the sneak peek of it.

alt_text

Freshworks developer platform provides various features for its frontend apps. Apps rendered in CTI placeholder need features that are very specific to the nature of Cloud Telephony requirements. For example, by clicking on a phone number in a contact, the app should open and dial in the number to make a call.

Open the cloned app in your favorite text editor and peek into the known component of the frontend app.

App Placeholder

Global Data Methods

Since the CTI apps load in the global sidebar location, they may require some data in all the pages such as the user who has logged in and the domain name of the Freshworks CRM account. The following data can be fetched from the app on all the pages.

We will use these data in the app where they are required.

Freshworks apps can interact with the other components in the same page or the page itself with some of the features provided by the platform. Let's check out some of them that are specifically useful for CTI apps.

Show App

When the app needs to be opened programmatically, this interface can be used. For example, the app may want to open the app and dial in the phone number when the agent clicks on the phone number of any contact in the ticket details page.

Copy and paste the following code snippet into the "openApp()" function to open the app programmatically.

client.interface.trigger("show", { id: "phoneApp" })
  .then(function () {
    console.info('Success: Opened the app');
  }).catch(function (error) {
    console.error('Error: Failed to open the app');
    console.error(error);
  });

Hide App

Similarly, the app would need to be closed programmatically when a certain workflow ends in the app such as call ending, deliberate minimization, etc.

Copy and paste the following code snippet into the "closeApp()" function to close the app programmatically.

 client.interface.trigger("hide", { id: "phoneApp" }).then(function (data) {
   console.info('successfully closed the CTI app');
   showNotify('success', 'Successfully closed the CTI app.');
 }).catch(function (error) {
   console.error('Error: Failed to close the CTI app');
   console.error(error);
 });

Check it out by clicking on the "Close app" button in the app.

alt_text

All the other interfaces are also available for CTI apps. Check out them in the Interfaces method tutorial on how to use them.

Users expect the system to automatically call the phone numbers in any user interface upon clicking on the phone numbers. The click-to-call event feature lets the app implement a similar experience for its users.

The event calling triggers when the user clicks on the phone number in specific pages in Freshworks CRM. It will provide the chosen phone number that can be used to fill in the dialer interface of the app or directly make a call to the phone number.

Copy and paste the following code snippet in the "clickToCall()" function to handle the event when a phone number is clicked on certain pages.

const textElement = document.getElementById('appText');

client.events.on("calling", function (event) {
  openApp();
  const phoneNumber = event.data.phoneNumber;
  textElement.innerText = `Clicked phone number: ${phoneNumber}`;
  showNotify('info', `Successfully captured phone number: ${phoneNumber} through click-to-call method`);
});

To test this functionality, go to the Ticket details page and click on the phone number shown in the right side Contact Details section and you can notice the app opens up and the phone number is displayed.

alt_text

Note: Ensure the "?dev=true" query parameter to the URL is kept appended to render the app running locally.

Showing all the contacts and their phone numbers in the CTI app is a common trend. The following Freshworks CRM API lets you view the contacts lists in the app by fetching all the existing contacts in the Freshworks CRM account.

This process has two steps as contacts are classified into different filtered views in Freshworks CRM, they can be fetched by their view identifier.

Let's get all the filtered views available in the account along with their details. Notice the "getContactFilters()" method fetching the contact filters.

function getContactFilters() {
  const url = 'https://<%= iparam.fcrm_domain %>/crm/sales/api/contacts/filters';
  const options = {
    headers: {
      Authorization: 'Token token=<%= iparam.fcrm_api_key %>'
    }
  }
  return client.request.get(url, options);
}

Now, let's fetch the contacts by a filtered view. Copy and paste the following code snippet in the "getContacts()" function to fetch the contacts from Freshworks CRM.

getContactFilters().then(function (data) {
  const filter = JSON.parse(data.response).filters.find(filter => filter.name === 'All Contacts');
  const url = `https://<%= iparam.fcrm_domain %>/crm/sales/api/contacts/view/${filter.id}`;
  const options = {
    headers: {
      Authorization: 'Token token=<%= iparam.fcrm_api_key %>'
    }
  }
  client.request.get(url, options).then(function (data) {
    console.info('Success: Got contacts');
    console.table(JSON.parse(data.response).contacts);
    showNotify('info', 'Successfully got the contacts list');
  }, function (error) {
    console.error('Error: Failed to get contacts');
    console.error(error);
  });
}, function (error) {
  console.error('Error: Failed to get contact filters');
  console.error(error);
});

Check the contacts being printed out in the browser console by clicking on the "Get Contacts" button in the app.

alt_text

There might be a need to create a contact if it is not available and called by the agent manually typing a phone number. The Create Contact API helps create a contact in the Freshworks CRM account.

Copy and paste the following code snippet in the "createContact()" function to create a contact in Freshworks CRM.

const url = `https://<%= iparam.fcrm_domain %>/crm/sales/api/contacts`;
const options = {
  headers: {
    Authorization: 'Token token=<%= iparam.fcrm_api_key %>'
  },
  json: { "contact": { "first_name": "sample", "last_name": "person", "mobile_number": "1-111-111-2222" } }
}
client.request.post(url, options).then(function (data) {
  console.info('Success: Created a contact');
  console.info(data);
  const contact = data.response.contact || JSON.parse(data.response).contact;
  console.table(contact);
  goToContact(contact.id);
}, function (error) {
  console.error('Error: Failed to create a contact');
  console.error(error);
});

Click on the "Create Contact" button in the app to test this functionality and go to the Contacts page in Freshworks CRM to check the newly created contact.

If a contact is created without the agent's knowledge, it would confuse the agents addressing the customers' calls. So, let's redirect the agent to the created contact page.

Copy and paste the following code snippet in the "goToContact()" function to navigate the user to the created contact details page.

client.interface.trigger("show", { id: "contact", value: contactId })
  .then(function (data) {
    console.info('successfully navigated to contact');
    console.info(data);
    showNotify('info', 'Successfully navigated to a contact');
  }).catch(function (error) {
    console.error('Error: Failed to navigate to contact');
    console.error(error);
  });

This will keep the agent informed by redirecting the user to the created contact and taking further actions from there.

Grab the Contact ID from any of the previously created contacts and assign it to the "id" attribute of "CONTACT'' constant in the "onAppActivated()" function. Next, click on the "Go to Contact" button to test this functionality and see the page navigating to the particular contact.

To record the call activities, call logs can be manually added to Freshworks CRM and linked to a contact.

Add this code to the "addPhoneCall()" function to add a manual call log to a contact.

client.data.get("loggedInUser").then(
  function (data) {
    const userId = data.loggedInUser.id;
    const url = `https://<%= iparam.fcrm_domain %>/crm/sales/api/cti_phone_calls`;
    const options = {
      headers: {
        Authorization: 'Token token=<%= iparam.fcrm_api_key %>',
        'Content-Type': 'multipart/form-data'
      },
      formData: {
        phone_call: {
          call_direction: call.type.toLowerCase().includes("incoming") || call.type.toLowerCase().includes("inbound"),
          targetable_type: 'Contact',
          targetable: {
            id: contact.id,
            first_name: contact.first_name,
            last_name: contact.last_name
          },
          note: {
            description: note
          },
          number: call.phone_number,
          user_id: userId,
          source: 'CTICOMPANYNAME'
        }
      }
    };
    client.request.post(url, options).then(function () {
      console.info('Success: Created a call log entry');
      showNotify('info', 'Successfully add a call log entry');
    }, function (error) {
      console.error('Error: Failed to add a call log');
      console.error(error);
      showNotify('danger', 'Failed to add a call log');
    });
  },
  function (error) {
    console.error('Error: Failed to get the loggedInUser information');
    console.error(error);
  });

Now, a call log has been added to the particular contact.

alt_text

Similar to call activities, sales activities can be added to entities including a contact to keep track of the entire lifecycle of a contact.

Sales activity types and outcomes are configured by the admins in Freshworks CRM. So, their identifier has to be dynamically fetched to tag the sales activity to a type and its come. Notice that we are also fetching the types and outcomes of the sales activities before adding the sales activities.

Add this code to the "addSalesActivity()" function to add a manual call log to a contact.

client.data.get("loggedInUser").then(
  function (data) {
    const userId = data.loggedInUser.id;
    getSalesActivityTypes().then(function (data) {
      const salesActivityTypes = JSON.parse(data.response).sales_activity_types;
      const salesActivityType = salesActivityTypes.find(activityType => activityType.internal_name === 'cphone');
      getSalesActivityOutcomes(salesActivityType.id).then(function (data) {
        const salesActivityOutcomes = JSON.parse(data.response).sales_activity_outcomes;
        const salesActivityOutcome = salesActivityOutcomes.find(salesActivityOutcome => salesActivityOutcome.name === "Interested");
        const url = `https://<%= iparam.fcrm_domain %>/crm/sales/api/sales_activities`;
        const options = {
          headers: {
            Authorization: 'Token token=<%= iparam.fcrm_api_key %>'
          },
          json: {
            sales_activity: {
              title: 'call',
              notes: note,
              targetable_type: 'Contact',
              targetable_id: contactId,
              start_date: '2017-12-04T17:00:00+05:30',
              end_date: '2017-12-04T17:00:00+05:30',
              owner_id: userId,
              sales_activity_type_id: salesActivityType.id,
              sales_activity_outcome_id: salesActivityOutcome.id
            }
          }
        }
        client.request.post(url, options).then(function (data) {
          console.info('Success: Created a sales activity');
          console.info(data);
          showNotify('info', 'Successfully add a sales activity');
        }, function (error) {
          console.error('Error: Failed to create a sales activity');
          console.error(error);
        });
      }, function (error) {
        console.error('Error: Failed to get all sales activity outcomes in account');
        console.error(error);
        showNotify('danger', 'Failed to add a sales activity');
      });
    }, function (error) {
      console.error('Error: Failed to get all sales activity types in account');
      console.error(error);
    });
  },
  function (error) {
    console.error('Error: Failed to get the loggedInUser information');
    console.error(error);
  });

Now, a sales activity has been added to the contact.

alt_text

Awesome! We have come to the final section. You have learned a bunch of features to build a CTI app on the Freshworks Developer Platform.

See the final source code of the app

These are the areas that are covered in this tutorial:

  1. App Placeholder
  2. Global Data Methods
  3. Interface methods to show/hide the app.
  4. Click-to-call event
  5. Get Contacts API
  6. Create Contact API
  7. Navigating to a contact page
  8. Add phone call and sales activities APIs