Click to Call via Web Browser

Help your customers and call centers quickly reach out via web browser or app

In this how-to guide, we will guide you through implementing a click to call application using FreeClimb. Click to call applications are great for customers who want to quickly chat with a support team and, conversely, for businesses and call centers that want to reach out to their customers in a more personal and efficient manner. This system will perform the following actions:

  • Get phone numbers from the user using a webpage
  • Create an outgoing call using the FreeClimb API
  • Create a conference using PerCL
  • Connect an agenct and caller in a conference using PerCL
You can also find the code for this sample app on GitHub


You're ready for this how-to guide if you have the following:

A FreeClimb account
A registered application with a named alias
A configured FreeClimb number
Trial accounts: A verified number
Your language and tools:

Step 1: App set-up

Make your server locally accessible

The fastest way to start testing your FreeClimb application is to temporarily make your local server publicly accessible through a tunneling service. We'll use ngrok to do this.

Once you have downloaded ngrok and unzipped the file to install it, open your terminal and navigate to the directory where you've unzipped ngrok. Use the following command to start a HTTP tunnel on port 3000:

ngrok http 3000

Once you run ngrok using the above command, you should receive a response with a public URL that looks something like this:

ngrok by @inconshreveable
Tunnel Status online
Version 2.0/2.0
Web Interface
Forwarding -> localhost:3000
Forwarding -> localhost:3000
Connnections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00

The forwarding URLs provided point to your local server. Save the URLs and move on to the next step. Make sure to keep ngrok open as your continue to build and run your app.

Configure your application's endpoints

Now that you've got a public URL, you're ready to configure your application's endpoints. We'll be configuring the voiceUrl using your ngrok URL and the route reference /incomingCall.

Go to the Apps page in your dashboard. You should see your registered FreeClimb app.


Your Apps page with a registered app.

Click Edit Config to enter the ngrok forwarding URL into your registered app's voiceUrl field and add the route /incomingCall to the end of it. When you're done, the App Config should look something like this:


Example of a completed App Config.

Save your updated App Config and proceed with the next steps.

Get dependencies

To start, create a new directory for the project. Within this directory create a file named package.json and add the following content:

  "name": "Node_Click_To_Call_How_To_Guide",
  "version": "1.0.0",
  "description": "Basic click to call how-to guide",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "@freeclimb/sdk": "^1.1.1",
    "body-parser": "^1.19.0",
    "dotenv": "^8.2.0",
    "dotenv-safe": "^8.2.0",
    "express": "^4.17.1",
    "express-handlebars": "^5.2.0"

These dependencies can be installed from the command line by using the following command:

yarn start

Set environment variables

In order to authenticate your requests with the FreeClimb platform, we need to include your API credentials (account ID and API key) in every request. Your account ID and API key can be found in the FreeClimb dashboard.


Why put API credentials in environment variables?

If you were to push your code to a public repository, those credentials would be public and an attacker could steal your account. To prevent this, we create environment variables with your API credentials and import them into your code using dotenv-safe.

This sample app uses dotenv-safe to read in your credentials, and anything else you save, as environment variables. As required by dotenv-safe, before creating our .env file we must create a .env.example file that outlines the environment variables that will need to be present in the actual .env file.


You must include both a .env.example file and a .env file in order for dotenv-safe to work properly.

To do so, create a file in your project's directory named .env.example and add the following content:


Once you've created your .env.example file, create a .env file in the root directory of your repo. Make sure to add the file to your .gitignore file. Add your account ID and API key to your .env file.

We'll also include your configured app ID, FreeClimb number, port, and host.

If you haven't already, make sure to assign a registered application you configured with your ngrok URL to the configured FreeClimb number you will be using for this sample application. Use this app ID and FreeClimb number as your APP_ID variable and FC_NUMBER variable respectively.

The port number will allow you to specify the port on which the app will run. For example, if you were to enter PORT=3000 to view the app, you would direct your browser to http://localhost:3000.

If you've set up ngrok to expose a specific port from your local machine, use the public facing link provided by ngrok as your HOST variable.

Your .env file should look as follows:

FC_NUMBER="+15555550010" // make sure your FreeClimb number is in E.164 format

Save your files and continue to the next step.

Step 2: Create and share a FreeClimb SDK instance

Throughout this application, we will be using a FreeClimb SDK object to generate our PerCL responses. Rather than re-create this object in every file that needs it, we will instread create one instance and pass it using require where needed.

To do this, create a new file in your project directory called freeclimb.js and add the following:

const freeclimbSDK = require('@freeclimb/sdk')
const accountId = process.env.ACCOUNT_ID
const apiKey = process.env.API_KEY
const freeclimb = freeclimbSDK(accountId, apiKey)

module.exports = freeclimb

Step 3: Create the express server

For this application, we'll create a server to contain our application logic and render our web forms. As we move on, we'll add more functionality to this file.

To start, create a new file in your project directory called index.js and add the following:

const express = require('express')
const bodyParser = require('body-parser')
const handlebars = require('express-handlebars')
// uncomment the lines below when you are ready to add in FreeClimb API functions in Step 7
// const freeclimb = require('./freeclimb') 
// const calls = require('./calls')
// const conferences = require('./conferences')
const app = express()
app.engine('handlebars', handlebars({ defaultLayout: 'main' }))
app.set('view engine', 'handlebars')
app.use(bodyParser.urlencoded({ extended: true }))
const port = process.env.PORT || 3000
const host = process.env.HOST
const fcNumber = process.env.FC_NUMBER
app.get('/', (req, res) => {
if (process.env.NODE_ENV !== 'test') {
    app.listen(port, () => {
        console.log(`Starting server on port ${port}`)
module.exports = { app }

You can confirm your Express server works properly by running the following console command to start your application:

yarn start

Now open a browser and head to localhost://{your-port-from-.env}. You should see the text "hello world."

Congrats you've set up an Express server with Node!

Step 4: Set up your webpages & templates

To separate the application logic in the code from our webpages we use express-handelbars. This allows us to remove any complicated Javascript from our HTML forms. To setup the templates we'll be using in this app, create a directory named "views" within your project. Then, inside the "views" directory create another directory named "layouts."

Create a file called main.handlebars inside the "layouts" directory with the following content. This is a basic layout that will contain all the pages for this application:

<!doctype html>
        <title>Freeclimb Click To Call Sample</title>
        <h1>FreeClimb Click To Call Sample</h1>

Get a user's phone number

We will be using a 2FA process to obtain the user's phone number. To do this create an HTML template with the following content in your "views" directory named inputPhone.handlebars. This template contains a form with two inputs and one button for form submission as well as a block that can be used to conditionally render error messages (denoted by the {{#if error }} ). We'll use this if we need to re-render the form in the event the user enters an invalid phone number.

On submission of this form, a POST request will be issued to the /send-verification endpoint containing the user-entered phone number (shown in the form tag as method="post" action="/send-verification"):

<p>Please enter your phone number and the phone number of the desired agent in E.164 format (e.g. +1234567891)</p>
<form method="post" action="/startCall">
    <input type="tel" name="caller" placeholder="Your number" />
    <br />
    <input type="tel" name="agent" placeholder="Agent Number" />
    <br />
    <input type="submit" value="Submit" />

{{#if error}}

Success message

We'll also create a template to render a success message to the user in the event the outbound call successfully connects. This form will also contain a button to bring the user back to the starting page if they wish to make another call. To do this, create another HTML template in your views directory named callMade.handlebars and add the following:

<p>call made successfully</p>
<form method="post" action="/reset">
    <input type="submit" value="Make Another Call" />

Step 5: Initiate a call

For our click to call application, we will be using the FreeClimb API's Make a Call functionality to initiate the calling process. To do this, create a new file in your project directory called calls.js and add the following:

const freeclimb = require('./freeclimb')

exports.createCall = async (to, from, callback) => {
    const appId = process.env.APP_ID
    await freeclimb.api.calls.create(to, from, appId, {
        callConnectUrl: callback, // endpoint hit when the callee picks up
        ifMachine: freeclimb.enums.ifMachine.HANGUP // automatically hangup if caller doesnt answer

Step 6: Handle hang up

Another important function of our click to call application is updating the status of our call in the event that either caller hangs up. We will do this by updating the Conference that both calls are part of. To do this, create a new file in your project directory called conferences.js and add the following:

const freeclimb = require('./freeclimb')

exports.terminate = async conferenceId => {
    const conference = await freeclimb.api.conferences.get(conferenceId)
    const status = conference.status

    if (status !== 'terminated') { // this prevents us from terminating a conference that has already ended
        await freeclimb.api.conferences.update(conferenceId, {
            status: freeclimb.enums.conferenceStatus.TERMINATED

Step 7: Render webpage templates

In order to make our original index.js file fully functional, we will add various routes to render webpages as well as handle the call and its users.

Import files

Before defining any endpoints we must import the files we use to interact with the FreeClimb API and call. Do this by uncommenting the following files in the header of your index.js files:

// uncomment the lines below when you are ready to add in FreeClimb API functions
const freeclimb = require('./freeclimb') 
const calls = require('./calls')
const conferences = require('./conferences')

Render webpages

The / endpoint will serve as our entryway into the application and will render our first template to allow the user to enter phone numbers (inputPhone.handlebars). First update the endpoint we defined for GET requests for the / route in Step 3. Instead of res.status(200).send('hello'), it should now be res.status(200).render('inputPhone'). See below:

app.get('/', (req, res) => {
    res.status(200).render('inputPhone') //no need to include .handlebars

Next we'll create the endpoint hit by the 'Make Another Call' button in callMade.handlebars. This route will simply re-render the inputPhone.handlebars form to its initial state. To do this, add the following to your index.js file:'/reset', (req, res) => {
    res.status(200).render('inputPhone') //no need to include .handlebars

Step 8: Start the call and create conference

FreeClimb connects calls by initiating an outbound call, creating a conference, and adding each callee (in this case, the agent and the user) to that conference.

To begin, we need to handle incoming requests to /startCall from the inputPhone.handlebars form submission. /startCall does this by using the FreeClimb API to make an outbound call to the agent phone number entered by the user.

Add the following code to index.js:'/startCall', async (req, res) => {
    const caller = req.body.caller
    const agent = req.body.agent
    try {
        await calls.createCall(agent, fcNumber, `${host}/agentPickup/${caller}`)
    } catch (err) {
        res.status(500).render('inputPhone', {
                'Your call could not be made. Please ensure you have correctly entered both phone numbers.'

Once the phone call has been initiated by the user via the inputPhone form and the agent outbound call has been initiated via /startCall, we'll need to create a conference in order to connect the agent and user.


How does FreeClimb connect two calls?

FreeClimb uses the OutDial and CreateConference PerCL commands to connect two or more callers.

Check out these resources to learn more about how FreeClimb connect calls:

First, we'll add an endpoint that returns the PerCL commands for creating a new conference. To do this, add the following to index.js:'/agentPickup/:caller', async (req, res) => {
    const caller = req.params.caller

Next we'll create the endpoint hit by the actionUrl of the createConference command used in /agentPickup/:caller. This will initiate an outbound call to the user.

To do this, add the following code to index.js:'/conferenceCreated/:caller', async (req, res) => {
    const conferenceId = req.body.conferenceId
    const caller = req.params.caller
                `${host}/userCalled/${conferenceId}`, // actionUrl invoked when outdial is made; keeps agent call leg alive
                `${host}/userConnected/${conferenceId}`, // callConnectedUrl invoked when callee is connected; references new call leg for user
                { ifMachine: freeclimb.enums.ifMachine.HANGUP }

Step 9: Connect agent and user calls

Now that we've created a conference and made outbound calls to both the agent and user, we need to add each into the conference we created in order to connect the two call legs.

To do this, we'll first create the endpoint hit by the actionUrl of the OutDial PerCL command from/conferenceCreated/:caller. This will:

  • add the agent into the conference we've already created
  • set the endpoint for when the agent and caller disconnect from the call

Add the following to index.js:'/userCalled/:conferenceId', (req, res) => {
    const conferenceId = req.params.conferenceId
    const callId = req.body.callId
            freeclimb.percl.say('Please wait while we attempt to add your client to the call'),
            freeclimb.percl.addToConference(conferenceId, callId, {
                leaveConferenceUrl: `${host}/leftConference`

Next, we'll create the endpoint hit by the callConnectedUrl of the OutDial PerCL command we used to call the user in /conferenceCreated/:caller. This will:

  • add the user into the conference
  • set the endpoint for when the agent and caller disconnect from the call

Add the following to index.js:'/userConnected/:conferenceId', async (req, res) => {
    const conferenceId = req.params.conferenceId
    const callId = req.body.callId
    if (req.body.dialCallStatus != freeclimb.enums.callStatus.IN_PROGRESS) {
        await conferences.terminate(conferenceId)
    } else {
                freeclimb.percl.addToConference(conferenceId, callId, {
                    leaveConferenceUrl: `${host}/leftConference`

Lastly, we'll create the endpoint hit by the leftConferenceUrl we set for both our user and agent when we placed them into the conference. This will invoke the FreeClimb API to terminate the conference. To do this, add the following to index.js:'/leftConference', async (req, res) => {
    const conferenceId = req.body.conferenceId
    await conferences.terminate(conferenceId)

Step 10: Run your app

To see your new click to call app in action, run the following command at the command line:

yarn start

Once you do this, direct your browser to localhost://{your-port-from-.env}. From there you should be able to enter phone numbers and make a call.


If you are trying to run this sample app while using a trial account, make sure that the number you use to test click to call is a verified number.

Congrats! You can now build your own custom click to call application. πŸ₯³πŸ₯³