Create a Conference and Add Participants

👍

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

A FreeClimb account
A registered application
A configured FreeClimb Number
Your tools and language installed


Node.js

In this how-to guide we will be using a single digit code obtained from the GetDigits PerCL command to add a user to one of three conference rooms, and create a Conference for the specified room if one does not currently exist. Once all participants have left a given conference, we will tell FreeClimb to terminate the conference.

For the purpose of storing conference room information, we will use a map of conference codes to an instance of a custom class defined below:

function ConferenceRoom() {
  // stores conferenceId associated with this room
  this.conferenceId = null
 
  // true if the CreateConference command was sent but the actionUrl has not yet been called, else false
  this.isConferencePending = false
 
  // Set to true after the conference status is first set to EMPTY, meaning that the next EMPTY status received indicates that all participants have left the conference and so the conference can terminate
  this.canConferenceTerminate = false
}

Create your package.json file and save in the root directory of your project:

{
  "name": "node-create-conference-how-to-guide",
  "version": "1.0.0",
  "license": "MIT",
  "dependencies": {
    "@freeclimb/sdk": "^3.8.0",
    "body-parser": "^1.20.3",
    "dotenv": "^16.4.5",
    "express": "^4.21.1"
  }
}

Install the package by running the following in the command line/terminal:

yarn install

Example code:

require('dotenv').config()
const express = require('express')
const bodyParser = require('body-parser')
const freeclimbSDK = require('@freeclimb/sdk')
const { Say, GetDigits, CreateConference, AddToConference, PerclScript, ConferenceStatus, UpdateConferenceRequest } = freeclimbSDK


const app = express()
app.use(bodyParser.json())
// Where your app is hosted ex. www.myapp.com
const host = process.env.HOST
const port = process.env.PORT || 3000
const hostUrl = `${host}:${port}`
// your freeclimb API key (available in the Dashboard) - be sure to set up environment variables to store these values
const accountId = process.env.ACCOUNT_ID
const apiKey = process.env.API_KEY

const configuration = freeclimbSDK.createConfiguration({ accountId, apiKey })
const freeclimb = new freeclimbSDK.DefaultApi(configuration)

First, get a conference access code (1, 2, or 3) from the user when they call into your number. See Accept an Incoming Call and Collect Digits for more information.

app.post('/incomingCall', (req, res) => {
  // Create PerCL say command
  const greeting = new Say({ text: 'Hello. Welcome to the conferences how-to guide, please enter your access code.' })
  // Create PerCL for getDigits command
  const getDigits = new GetDigits({
    actionUrl: `${hostUrl}/gotDigits`,
    maxDigits: 1,
    minDigits: 1,
    flushBuffer: true
  })
  // Build and respond with Percl command
  const percl = new PerclScript({ commands: [greeting, getDigits]}).build()
  res.status(200).json(percl)
})
app.post('/gotDigits', (req, res) => {
  const getDigitsResponse = req.body
  const digits = getDigitsResponse.digits
  const callId = getDigitsResponse.callId

  const room = conferenceRooms.get(digits)
  if (room === undefined) {
    // Handle case where no room with the given code exists
  }
  // if participants can't be added yet (actionUrl callback has not been called) notify caller and hang up
  if (room.isConferencePending) {
    const say = new Say({ text: 'We are sorry, you cannot be added to the conference at this time. Please try again later.' })
    const percl = new PerclScript({ commands: [say] }).build()
    res.status(200).json(percl)
  } else {
    const say = new Say({ text: 'You will be added to the conference momentarily.' })
    const makeOrAddToConfScript = makeOrAddToConference(room, digits, callId)
    const percl = new PerclScript({ commands: [say, makeOrAddToConference] }).build()
    res.status(200).json(percl)
  }
})

If the entered code is invalid we notify the caller via a Say command and hang up, otherwise we add either a CreateConference or AddToConference to the script and then return the JSON formatted script in the response. Note that we can only add a participant to a conference after a conference’s actionUrl callback has been called.

function makeOrAddToConference(room, roomCode, callId) {
  if (room.conferenceId == null) {
    // If a conference has not been created for this room yet, return a CreateConference PerCL command
    room.isConferencePending = true
    room.canConferenceTerminate = false
    // Create CreateConference PerCL command
    return new CreateConference({
      actionUrl: `${hostUrl}/conferenceCreated/${roomCode}`,
      statusCallbackUrl: `${hostUrl}/conferenceStatus/${roomCode}`
    })
  } else {
    // If a conference has been created and the actionUrl callback has been called, return a AddToConference PerCL command
    return new AddToConference({
      conferenceId: room.conferenceId,
      callId 
    })
  }
}

Once a conference is created, the actionUrl set in the CreateConference PerCL command is called with a ConferenceCreateActionCallback as payload. In this callback, we find the conference room which was just created, update its properties, and add a AddToConference PerCL command to add the initial caller as a participant.

app.post('/conferenceCreated/:roomCode', (req, res) => {
  const roomCode = req.params.roomCode
  const conferenceCreatedResponse = req.body
  const conferenceId = conferenceCreatedResponse.conferenceId
  const callId = conferenceCreatedResponse.callId
  // find which conference room the newly created conference belongs to
  const room = conferenceRooms.get(roomCode)

  if (room === undefined) {
    // Handle case where callback is called for a room that does not exist
  }

  room.conferenceId = conferenceId
  room.isConferencePending = false
  // Create AddToConference PerCL command
  const addToConference = new AddToConference({ conferenceId, callId })
  const percl = new PerclScript({ commands: [addToConference] }).build()
  res.status(200).json(percl)
})

The statusCallbackUrl callback will also be called with a ConferenceStatusCallback as payload when the conference is created, as well as when the state of the conference changes. See Conferences in the API Reference for more information on when and how a conference status can change. Requests to this endpoint do not expect anything to be returned.

In this callback we check the ConferenceStatus, and if the status is EMPTY we terminate the conference only if the EMPTY status is due to all participants leaving and not due to the conference being created.

app.post('/conferenceStatus/:roomCode', (req, res) => {
  const roomCode = req.params.roomCode
  const conferenceStatusResponse = req.body
  const status = conferenceStatusResponse.status
  const conferenceId = conferenceStatusResponse.conferenceId

  // find which conference room the conference belongs to
  const room = conferenceRooms.get(roomCode)

  if (room === undefined) {
    // Handle case where callback is called for a room that does not exist
  }

  if (status === ConferenceStatus.EMPTY && room.canConferenceTerminate) {
    terminateConference(conferenceId)
  }
  // after first EMPTY status update conference can be terminated
  room.canConferenceTerminate = true
  res.status(200)
})

To terminate a conference, we use the FreeClimb API to make a POST request to the specified conference and update its status to TERMINATED.

function terminateConference(conferenceId) {
  // Create the ConferenceUpdateOptions and set the status to terminated
  const options = new UpdateConferenceRequest({
    status: ConferenceStatus.TERMINATED
  })
  return freeclimb.updateAConference(conferenceId, options)
}

Handle status updates:

// Specify this route with 'Status Callback URL' in App Config
app.post('/status', (req, res) => {
  // handle status changes
  res.status(200)
})

Start the server:

app.listen(port, () => {
  console.log(`Starting server on port ${port}`)
})

Java

In this how-to guide we will be using a single digit code obtained from the GetDigits PerCL command to add a user to one of three conference rooms, and create a Conference for the specified room if one does not currently exist. Once all participants have left a given conference, we will tell FreeClimb to terminate the conference.

Create your build.gradle file and save it to the root directory in your project:

/*
 * This file was generated by the Gradle 'init' task.
 *
 * This is a general purpose Gradle build.
 * Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds
 */

buildscript {
    repositories {
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
  //Add the dependency
    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:2.1.6.RELEASE"
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

bootJar {
    baseName = 'gs-spring-boot'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
    maven { url 'https://jitpack.io' }
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    compile "org.springframework.boot:spring-boot-starter-web"
    testCompile "junit:junit"
    'com.github.FreeClimbAPI:FreeClimb-Java-SDK:3.0.0'
}

sourceSets {
    main {
        java {
            srcDirs = ['src'] // changed line
        }
    }
}

Build the file by running the following in your terminal/command line:

gradle build

Example code:

/* 
 * 1. RUN PROJECT WITH COMMAND: 
 *    `gradle build && java -Dserver.port=0080 -jar build/libs/gs-spring-boot-0.1.0.jar`
 * 2. CALL FreeClimb NUMBER ASSOCIATED WITH THE ACCOUNT (CONFIGURED IN FreeClimb DASHBOARD)
 * 3. EXPECT PROMPT FOR ACCESS CODE TO BE REPEATED TO YOU
 *    ENTER ONE OF THREE conferenceRoomCodes (1, 2, OR 3)
 * 4. EXPECT MESSAGE:
 *    "You will be added to the conference momentarily."
 *    EXPECT A NEW CONFERENCE UNDER YOUR FreeClimb ACCOUNT, WHICH CAN BE FOUND IN FreeClimb DASHBOARD
*/

package main.java.create_conference;

import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

import com.vailsys.freeclimb.percl.*;

import com.vailsys.freeclimb.webhooks.percl.GetDigitsActionCallback;
import com.vailsys.freeclimb.webhooks.conference.ConferenceCreateActionCallback;
import com.vailsys.freeclimb.webhooks.conference.ConferenceStatusCallback;

import com.vailsys.freeclimb.api.conference.ConferenceStatus;
import com.vailsys.freeclimb.api.FreeClimbClient;
import com.vailsys.freeclimb.api.conference.ConferenceUpdateOptions;
import com.vailsys.freeclimb.api.FreeClimbException;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.LinkedList;

For the purpose of storing conference room information, we will use a map of conference codes to an instance of a custom class defined below:

@RestController
public class CreateConferenceController {
  // Get base URL from environment variables
  private String baseUrl = System.getenv("HOST");
  private static final String[] conferenceRoomCodes = { "1", "2", "3" };
  private static HashMap<String, ConferenceRoom> conferenceRooms = new HashMap<String, ConferenceRoom>();

  private class ConferenceRoom {
    // stores conferenceId associated with this room
    public String conferenceId = null;

    // true if the CreateConference command was sent but the actionUrl has not yet
    // been called, else false
    public Boolean isConferencePending = false;

    // Set to true after the conference status is first set to EMPTY, meaning that
    // the next EMPTY status received indicates that all participants have left the
    // conference and so the conference can terminate
    public Boolean canConferenceTerminate = false;

  }

First, get a conference access code (1, 2, or 3) from the user when they call into your number. See Accept an Incoming Call and Collect Digits for more information.

// To properly communicate with FreeClimb's API, set your FreeClimb app's
  // VoiceURL endpoint to '{yourApplicationURL}/InboundCall' for this example
  // Your FreeClimb app can be configured in the FreeClimb Dashboard
  @RequestMapping(value = {
      "/InboundCall" }, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  public ResponseEntity<?> inboundCall() {

    // Add three ConferenceRooms to the map on initialization
    for (String code : conferenceRoomCodes) {
      conferenceRooms.put(code, new ConferenceRoom());
    }

    // Create an empty PerCL script container
    PerCLScript script = new PerCLScript();

    // Create PerCl getdigits script
    GetDigits getDigits = new GetDigits(baseUrl + "/GotDigits");

    getDigits.setMaxDigits(1);

    LinkedList<GetDigitsNestable> prompts = new LinkedList<>();
    prompts.add(new Say("Hello. Welcome to the conferences tutorial, please enter your access code."));
    getDigits.setPrompts(prompts);

    script.add(getDigits);

    // Convert PerCL container to JSON and append to response
    return new ResponseEntity<>(script.toJson(), HttpStatus.OK);
  }
@RequestMapping(value = {
      "/GotDigits" }, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  public ResponseEntity<?> gotDigits(@RequestBody String str) {
    PerCLScript script = new PerCLScript();

    GetDigitsActionCallback getDigitsActionCallback;
    try {
      // Convert JSON into a call status callback object
      getDigitsActionCallback = GetDigitsActionCallback.createFromJson(str);

      String digits = getDigitsActionCallback.getDigits();
      String callId = getDigitsActionCallback.getCallId();

      ConferenceRoom room = conferenceRooms.get(digits);
      if (room == null) {
        // Handle case where no room with the given code exists
      }

      // if participants can't be added yet (actionUrl callback has not been called)
      // notify caller and hang up
      if (room.isConferencePending) {
        script
            .add(new Say("We are sorry, you cannot be added to the conference at this time. Please try again later."));
        script.add(new Hangup());
      } else {
        script.add(new Say("You will be added to the conference momentarily."));
        script.add(makeOrAddToConference(room, digits, callId));
      }

    } catch (FreeClimbException pe) {
      System.out.print(pe);
    }

    return new ResponseEntity<>(script.toJson(), HttpStatus.OK);
  }

If the entered code is invalid we notify the caller via a Say command and hang up, otherwise we add either a CreateConference or AddToConference to the script and then return the JSON formatted script in the response. Note that we can only add a participant to a conference after a conference’s actionUrl callback has been called.

private static PerCLCommand makeOrAddToConference(ConferenceRoom room, String roomCode, String callId) {
    // If a conference has not been created for this room yet, return a
    // CreateConference PerCL command
    if (room.conferenceId == null) {
      room.isConferencePending = true;
      room.canConferenceTerminate = false;

      CreateConference createConference = new CreateConference(
          System.getenv("HOST") + "/ConferenceCreated/" + roomCode);
      createConference.setStatusCallbackUrl(System.getenv("HOST") + "/ConferenceStatus/" + roomCode);
      return createConference;
    } else {
      // If a conference has been created and the actionUrl callback has been called,
      // return a AddToConference PerCL command
      return new AddToConference(room.conferenceId, callId);
    }
  }

Once a conference is created, the actionUrl set in the CreateConference PerCL command is called with a ConferenceCreateActionCallback as payload. In this callback, we find the conference room which was just created, update its properties, and add a AddToConference PerCL command to add the initial caller as a participant.

@RequestMapping(value = {
      "/ConferenceCreated/{roomCode}" }, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  public ResponseEntity<?> conferenceCreated(@PathVariable String roomCode, @RequestBody String str) {
    PerCLScript script = new PerCLScript();

    ConferenceCreateActionCallback conferenceCreateActionCallback;
    try {
      conferenceCreateActionCallback = ConferenceCreateActionCallback.createFromJson(str);
      String conferenceId = conferenceCreateActionCallback.getConferenceId();

      // find which conference room the newly created conference belongs to
      ConferenceRoom room = conferenceRooms.get(roomCode);

      if (room == null) {
        // Handle case where callback is called for a room that does not exist
      }

      room.conferenceId = conferenceId;
      room.isConferencePending = false;

      // Add initial caller to conference
      script.add(new AddToConference(conferenceId, conferenceCreateActionCallback.getCallId()));

    } catch (FreeClimbException pe) {
      System.out.print(pe);
    }

    return new ResponseEntity<>(script.toJson(), HttpStatus.OK);
  }

The statusCallbackUrl callback will also be called with a ConferenceStatusCallback as payload when the conference is created, as well as when the state of the conference changes. See Conferences in the API Reference for more information on when and how a conference status can change. Requests to this endpoint do not expect anything to be returned.

In this callback we check the ConferenceStatus, and if the status is EMPTY we terminate the conference only if the EMPTY status is due to all participants leaving and not due to the conference being created.

@RequestMapping(value = {
      "/ConferenceStatus/{roomCode}" }, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
  public ResponseEntity<?> conferenceStatus(@PathVariable String roomCode, @RequestBody String str) {

    ConferenceStatusCallback conferenceStatusCallback;
    try {
      conferenceStatusCallback = ConferenceStatusCallback.createFromJson(str);
      ConferenceStatus status = conferenceStatusCallback.getStatus();
      String conferenceId = conferenceStatusCallback.getConferenceId();

      // find which conference room the conference belongs to
      ConferenceRoom room = conferenceRooms.get(roomCode);

      if (room == null) {
        // Handle case where callback is called for a room that does not exist
      }

      if (status.equals(ConferenceStatus.EMPTY) && room.canConferenceTerminate) {
        try {
          terminateConference(conferenceId);
          room.conferenceId = null;
        } catch (FreeClimbException pe) {
          // Handle error when terminateConference fails
          System.out.print(pe);
        }
      }

      // after first EMPTY status update conference can be terminated
      room.canConferenceTerminate = true;
    } catch (FreeClimbException pe) {
      System.out.print(pe);
    }

    return new ResponseEntity<>("", HttpStatus.OK);
  }

To terminate a conference, we use a FreeClimbClient to make a POST request to the specified conference and update its status to TERMINATED.

private static void terminateConference(String conferenceId) throws FreeClimbException {
    String accountId = System.getenv("ACCOUNT_ID");
    String apiKey = System.getenv("API_KEY");
    FreeClimbClient client = new FreeClimbClient(accountId, apiKey);

    // Create the ConferenceUpdateOptions and set the status to terminated
    ConferenceUpdateOptions conferenceUpdateOptions = new ConferenceUpdateOptions();
    conferenceUpdateOptions.setStatus(ConferenceStatus.TERMINATED);
    client.conferences.update(conferenceId, conferenceUpdateOptions);
  }
}

C#

In this how-to guide we will be using a single digit code obtained from the GetDigits PerCL command to add a user to one of three conference rooms, and create a conference for the specified room if one does not currently exist. Once all participants have left a given conference, we will tell FreeClimb to terminate the conference.

All the methods in this class were created in a ASP.NET Core MVC application in one controller, ConferencesController.

Imports used:

using System;
using System.Collections.Generic;
using com.freeclimb;
using com.freeclimb.percl;
using com.freeclimb.webhooks.call;
using com.freeclimb.webhooks.conference;
using com.freeclimb.webhooks.percl;
using com.freeclimb.api;
using com.freeclimb.api.conference;
using Microsoft.AspNetCore.Mvc;

For the purpose of storing conference room information, we will use a map of conference codes, created by calling the init() method below, to an instance of a custom class defined below:

public class ConferenceRoom
{
    // stores conferenceId associated with this room
    public string conferenceId = null;
 
    // true if the CreateConference command was sent but the actionUrl has not yet been called, else false
    public bool isConferencePending = false;
 
    // Set to true after the conference status is first set to EMPTY, meaning that the next EMPTY status received indicates that all participants have left the conference and so the conference can terminate
    public bool canConferenceTerminate = false;
}

The static class variables below are defined on the controller class that implements this how-to guide. The static constructor calls the init method before the first instance of the controller class is created.

private static string[] conferenceRoomCodes = {"1", "2", "3"};
private static IDictionary<string, ConferenceRoom> conferenceRooms = new Dictionary<string, ConferenceRoom>();
 
static ConferencesController()
{
    init();
}
 
private void init()
{
    conferenceRooms.Clear();
    // Add three ConferenceRooms to the map on initialization
    foreach (string code in conferenceRoomCodes)
    {
        conferenceRooms.Add(code, new ConferenceRoom());
    }
}

First, get a conference access code (1, 2, or 3) from the user when they call into your number. See Accept an Incoming Call and Collect Digits for more information on receiving the call and getting user input.

Example code:

[HttpPost ("InboundCall")] //POST /voice/InboundCall
public ActionResult InboundCall (CallStatusCallback callStatus) {
  PerCLScript script = new PerCLScript ();
  var getDigitsActionUrl = getAppUrl() + "/voice/GetDigitsDone";
  GetDigits getDigits = new GetDigits (getDigitsActionUrl);
  getDigits.setMaxDigits (1);
  Say say = new Say ();
  say.setText ("Hello. Welcome to the conferences tutorial, please enter your access code.");
  getDigits.setPrompts (say);
  script.Add (getDigits);
  return Content (script.toJson (), "application/json");
}

[HttpPost ("GetDigitsDone")] // POST /voice/GetDigitsDone
public ActionResult GetDigitsDone (GetDigitsActionCallback request) {
  // Make OutDial request once conference has been created
  PerCLScript script = new PerCLScript ();
  string callId = request.getCallId;
  string digits = request.getDigits;
  ConferenceRoom room = conferenceRooms[digits];
  if (room == null) {
    // Handle case where no room with the given code exists
  }

  // if participants can't be added yet (actionUrl callback has not been called) notify caller and hang up
  if (room.isConferencePending) {
    Say say = new Say ();
    say.setText ("We are sorry, you cannot be added to the conference at this time. Please try again later.");
    script.Add (say);
    script.Add (new Hangup ());
  } else {
    Say say = new Say ();
    say.setText ("You will be added to the conference momentarily.");
    script.Add (say);
    script.Add (makeOrAddToConference (room, digits, callId));
  }
  return Content (script.toJson (), "application/json");
}

If the entered code is invalid we notify the caller via a Say command and hang up, otherwise we add either a CreateConference or AddToConference to the script and then return the JSON formatted script in the response. Note that we can only add a participant to a conference after a conference’s actionUrl callback has been called.

Example code:

private PerCLCommand makeOrAddToConference (ConferenceRoom room, String roomCode, String callId) {
  // If a conference has not been created for this room yet, return a CreateConference PerCL command
  if (room.conferenceId == null) {
    room.isConferencePending = true;
    room.canConferenceTerminate = false;
    var conferenceActionUrl = getAppUrl() + $"/voice/ConferenceCreated?roomCode={roomCode}";
    var conferenceStatusUrl = getAppUrl() + $"/voice/ConferenceStatus?roomCode={roomCode}";
    CreateConference createConference = new CreateConference (conferenceActionUrl);
    createConference.setStatusCallbackUrl (conferenceStatusUrl);
    return createConference;
  } else {
    // If a conference has been created and the actionUrl callback has been called, return a AddToConference PerCL command
    return new AddToConference (room.conferenceId, callId);
  }
}

Once a conference is created, the actionUrl set in the CreateConference PerCL command is called with a ConferenceCreateActionCallback as payload. In this callback, we find the conference room which was just created, update its properties, and add a AddToConference PerCL command to add the initial caller as a participant.

[HttpPost("ConferenceCreated")] // POST /voice/ConferenceCreated
    public ActionResult ConferenceCreated ([FromQuery (Name = "roomCode")] string roomCode, ConferenceCreateActionCallback request) {
      PerCLScript script = new PerCLScript ();
      string conferenceId = request.getConferenceId;
      string callId = request.getCallId;
      // find which conference room the newly created conference belongs to
      ConferenceRoom room = conferenceRooms[roomCode];
      if (room == null) {
        // Handle case where callback is called for a room that does not exist
      }
      room.conferenceId = conferenceId;
      room.isConferencePending = false;

      Say welcomeToConference = new Say();
      welcomeToConference.setText("You are now being added to the conference");
      script.Add(welcomeToConference);
      // Add initial caller to conference
      script.Add (new AddToConference (conferenceId, request.getCallId));
      return Content (script.toJson (), "application/json");
    }

The statusCallbackUrl callback will also be called with a ConferenceStatusCallback as payload when the conference is created, as well as when the state of the conference changes. See Conferences in the API Reference for more information on when and how a conference status can change. Requests to this endpoint do not expect anything to be returned.

In this callback we check the ConferenceStatus, and if the status is EMPTY we terminate the conference only if the EMPTY status is due to all participants leaving and not due to the conference having been newly created.

[HttpPost("ConferenceStatus")]
    public ActionResult ConferenceStatus ([FromQuery (Name = "roomCode")] string roomCode, ConferenceStatusCallback request) {
      PerCLScript script = new PerCLScript ();
      EConferenceStatus status = request.getStatus;
      String conferenceId = request.getConferenceId;
      // find which conference room the conference belongs to
      ConferenceRoom room = conferenceRooms[roomCode];
      if (room == null) {
        // Handle case where callback is called for a room that does not exist
      }
      if (status == EConferenceStatus.Empty && room.canConferenceTerminate) {
        try {
          terminateConference (conferenceId);
          room.conferenceId = null;
        } catch (FreeClimbException pe) {
          // Handle error when terminateConference fails
        }
      }
      // after first EMPTY status update conference can be terminated
      room.canConferenceTerminate = true;
      return Content (script.toJson (), "application/json");
    }

To terminate a conference, we use a FreeClimbClient to make a POST request to the specified conference and update its status to TERMINATED.

private void terminateConference (string conferenceId) {
  // your credentials information filled in here
  string acctId = getAcctId ();
  string apiKey = getApiKey ();
  FreeClimbClient client = new FreeClimbClient (acctId, apiKey);
  // terminating a conference is done by changing the status to Terminated
  ConferenceOptions options = new ConferenceOptions ();
  options.setStatus (com.freeclimb.EConferenceStatus.Terminated);
  client.getConferencesRequester.update (conferenceId, options);
}