Implementing the 3cX Call Control API

Implementing the 3cX Call Control API

Since you're reading this, there is a high chance that you need to implement a few kinds of stuff to control 3cX.

I must inform you that 3cX community does not want you to use the Call Control API. Instead, they suggest you use the HTTP Endpoints and I even have no idea where that is.

Here are the things you can do with the Call Control API:

The Call Control API is an API available for 3CX V11 and upwards that allows calls to be controlled programmatically. This is an advanced API that requires .NET, C# and telecommunications experience. It should only be used for larger projects. To integrate with CRM systems, use the 3CX CRM Plugin API.

Note: 3CX DOES NOT PROVIDE DEVELOPER SUPPORT for this API!

With the Call Control API, you can:

  • Make and End a call
  • View all active calls on the PBX
  • Take control of a call
  • Transfers
  • Disconnect a call
  • Programmatically configure certain settings in 3CX Phone System

Prerequisites

The API is a DLL/.NET library. In order to use it, you need to add some references: 3cxpscomcpp2.dll, sl.dll and tcxpscom_native.dll which are located in the Global Assembly cache (C:\Windows\Microsoft.NET\assembly\GAC_64).

Configuration

  • Specify the application name, which must be unique, in the 3CXphonesystem.ini file (located in the 3CX Phone System directory). Go to C:\Program Files\3CX Phone System\Instance1\Bin and copy 3CXPhoneSystem.ini to the Outputs folder of your Visual Studio project.
  • Edit 3CXPhoneSystem.ini and add to the section [ConfService] the application name, eg: PhoneSystem.ApplicationName = “UniqueName”

The communication over the TCP is handled by the library and is via port 5485. Important:

Note:

  • The API is only available on the server, i.e any application that is using it must be running on the server itself. This is because the service will not accept connections from 0.0.0.0 (ANY IP). Connections are only accepted from 127.0.0.1 (localhost).
  • The Call Control API is only available in commercial editions. 3CX API documentation, including sample applications that make the use of the API self-explanatory, can be found here:

    3CX API Documentation Package for 3CX V12

3CX API Documentation Package for 3CX V14

3CX API Documentation Package for 3CX V15

3CX API Documentation Package for 3CX V16

............. Basically, you just need to add the version number to the download URL to download the project.

3CX API Documentation Package for 3CX VN downloads.3cx.com/downloads/misc/callcontro..N.zip

I have uploaded the codes to GitHub in case the link fails.

github.com/fzany/3CXCallControlAPI_v12

github.com/fzany/3CXCallControlAPI_v14

github.com/fzany/3CXCallControlAPI_v15

github.com/fzany/3CXCallControlAPI_v16

The Object model samples included in this package show how to:

  • Add an extension with a set of properties
  • Remove extensions
  • Update extensions
  • Add / remove a digital receptionist
  • Add a Phone by model
  • How to change Parking dial codes
  • How to change Voicemail Box Information
  • Add Fax Extensions
  • Create forwarding rules and profiles
  • Create a new prompt set
  • How to use the call control API to Barge in
  • How to use the call control API to Divert calls
  • How to use the call control API to Drop calls
  • How to use the call control API to perform Listen or whisper functions
  • How to use the call control API to Make calls
  • How to record calls
  • Transferring of calls by DN
  • Transferring of calls by ActiveConnection
  • Schedule a conference
  • and many other examples…

The first step in using the Call Control API is initializing. Remember it must be run on the server. I used the ConnectTo3cX method.

  public static void ConnectTo3cX(int wait)
        {
            try
            {
                var filePath = @"C:\Program Files\3CX Phone System\Bin\3CXPhoneSystem.ini"; // @".\3CXPhoneSystem.ini";
                if (!File.Exists(filePath))
                {
                    //this code expects 3CXPhoneSystem.ini in current directory.
                    //it can be taken from the installation folder (find it in Program Files/3CXPhone System/instance1/bin for in premiss installation)
                    //or this application can be run with current directory set to location of 3CXPhoneSystem.ini

                    //v14 (cloud and in premiss) installation has changed folder structure.
                    //3CXPhoneSystem.ini which contains connectio information is located in 
                    //<Program Files>/3CX Phone System/instanceN/Bin folder.
                    //in premiss instance files are located in <Program Files>/3CX Phone System/instance1/Bin
                    Logger.Log("Cannot find 3CXPhoneSystem.ini");
                }
                else
                {
                    Logger.Log("Found 3CXPhoneSystem.ini");
                    ReadConfiguration(filePath);  
                    phoneSystem = Bootstrap(wait);
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }
        }

Here is the ReadConfiguration method.

static void ReadConfiguration(string filePath)
        {
            try
            {
                var content = File.ReadAllLines(filePath);
                Dictionary<string, string> CurrentSection = null;
                string CurrentSectionName = null;
                for (int i = 1; i < content.Length + 1; i++)
                {
                    var s = content[i - 1].Trim();
                    if (s.StartsWith("["))
                    {
                        CurrentSectionName = s.Split(new[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries)[0];
                        CurrentSection = iniContent[CurrentSectionName] = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
                    }
                    else if (CurrentSection != null && !string.IsNullOrWhiteSpace(s) && !s.StartsWith("#") && !s.StartsWith(";"))
                    {
                        var res = s.Split("=").Select(x => x.Trim()).ToArray();
                        CurrentSection[res[0]] = res[1];
                    }
                    else
                    {
                        //Logger.Log($"Ignore Line {i} in section '{CurrentSectionName}': '{s}' ");
                    }
                }
                instanceBinPath = Path.Combine(iniContent["General"]["AppPath"], "Bin");
                Logger.Log(instanceBinPath);
            }
            catch (Exception ex)
            {
                Logger.Log("error in ReadConfiguration");

                Logger.Log(ex);
            }
        }

Then Bootstrap

        public static PhoneSystem Bootstrap(int wait)
        {
            try
            {
                PhoneSystem.CfgServerHost = "127.0.0.1";
                PhoneSystem.CfgServerPort = int.Parse(iniContent["ConfService"]["ConfPort"]);
                PhoneSystem.CfgServerUser = iniContent["ConfService"]["confUser"];
                PhoneSystem.CfgServerPassword = iniContent["ConfService"]["confPass"];

                var a = new Random(Environment.TickCount);
                PhoneSystem.ApplicationName = "MiddleWareCallControl"; //any name
                PhoneSystem.ApplicationName += a.Next().ToString();

                var ps = PhoneSystem.Reset(
               PhoneSystem.ApplicationName + new Random(Environment.TickCount).Next().ToString(),
               "127.0.0.1",
               int.Parse(iniContent["ConfService"]["ConfPort"]),
               iniContent["ConfService"]["confUser"],
               iniContent["ConfService"]["confPass"]
//,
//                    (x, y) => Helpers.Logger.Log($"Inserted_-{y.EntityName}.{y.RecID}"),
//                    (x, y) => Helpers.Logger.Log($"Updated_-{y.EntityName}.{y.RecID}"),
//                    (x, y) => Helpers.Logger.Log($"Deleted_-{y.EntityName}.{y.RecID}")
);

                //ps.Inserted += (x, y) => Helpers.Logger.Log($"Inserted_-{y.EntityName}.{y.RecID}_custom");
                ps.Updated += (x, y) =>
                {
                    if (y.ConfObject != null)
                    {
                        //  Helpers.Logger.Log($"Updated-{y.EntityName}.{y.ConfObject.ToString()}_custom");

                        try
                        {

                            // Logger.Log(y.ConfObject.ToString());

                            //var test = (OMCallCollector.CallStateSnapshot)y.ConfObject;
                            //Logger.Log(test.Destination);


                        }
                        catch (Exception ex)
                        {

                        }

                        try
                        {
                            // Logger.Log(JsonConvert.SerializeObject( y.ConfObject));
                        }
                        catch (Exception ex)
                        {

                        }
                    }
                };
                ps.Deleted += (x, y) =>
                {

                    if (y.ConfObject != null)
                    {
                        //  Helpers.Logger.Log($"Updated-{y.EntityName}.{y.ConfObject.ToString()}_custom");

                        try
                        {

                            // Logger.Log(y.ConfObject.ToString());

                            //var test = (OMCallCollector.CallStateSnapshot)y.ConfObject;
                            //Logger.Log(test.Destination);


                        }
                        catch (Exception ex)
                        {

                        }

                        try
                        {
                            // Logger.Log(JsonConvert.SerializeObject(y.ConfObject));
                        }
                        catch (Exception ex)
                        {

                        }
                    }

                    //Helpers.Logger.Log($"Deleted_-{y.EntityName}.{y.RecID}_custom");

                };
                ps.WaitForConnect(TimeSpan.FromSeconds(wait));

                var dns = PhoneSystem.Root.GetDN(); //Access PhoneSystem.Root to initialize ObjectModel
                if (dns != null)
                {
                    Helpers.Logger.Log("Established Connection to 3cX Server.");
                    Monitor();
                    return PhoneSystem.Root;
                }
                else
                {
                    Helpers.Logger.Log("Connection to 3cX Server failed");

                }
                return null;
            }
            catch (Exception ex)
            {
                Logger.Log("error in Bootstrap");
                Helpers.Logger.Log(ex.ToString());
                return null;
            }
        }

Making calls

        internal static void Caller(string agent, string customer)
        {
//strip the 234 or international dialing code from the number
            if (customer.Substring(0, 3) == "234")
            {
                customer = customer.Remove(0, 3);
                customer = customer.Insert(0, "0");
            }
            Logger.Log($"{agent} is trying to call {customer}");

            Helpers.Logger.Log("Call initiating on Connection");
            try
            {
                Reconnect3CX();
                var all_reg = PhoneSystem.Root.GetAll<RegistrarRecord>().Where(d => d.DN != null).Where(f => f.DN.Number != null);
                if (all_reg.Any(d => d.DN.Number == agent))
                {
                    Logger.Log("A contact is found on the DN");
                    PhoneSystem.Root.MakeCall(all_reg.FirstOrDefault(d => d.DN.Number == agent), customer);
                }
                else
                {
                    Logger.Log("No contact is found on the DN");
                    PhoneSystem.Root.MakeCall(customer, agent);
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }

        }

Ending Calls

        internal static void HangupCall(CallMaker call)
        {
//here are various ways to hangup a call
            /* 
             * public void DropCall(ActiveConnection ac, DropRingingAction ringing_action);
                public void DropCall(int CallID, string dn);
                public void DropCall(ActiveConnection ac);*/
            // PhoneSystem.Root.DropCall(customer, agent);

            try
            {
                Reconnect3CX();
                PhoneSystem.Root.DropCall(call.CxID, call.AgentID);


            }
            catch (Exception ex)
            {

                Logger.Log(ex);
            }
            // PhoneSystem.Root.GetByID<ActiveConnection>(int.Parse(args[2])).Drop();

        }

I have implemented many other functions. Write a comment if you want the code for more.