Problem with tracking incoming/outgoing/originated calls through the Asterisk Management API

Hi guys and thank you for an awesome tool :slight_smile:

I have been given the task of integrating FreePBX and Asterisk into our inhouse developed CRM system, and one of the tasks I have been assigned is to track incoming, outgoing, and originated calls. After weeks of “shotgun debugging”, I’ve figured out the smart way to track calls is to use the Asterisk Management API`s “CDR event”, published when a call hangs up. At which point I’ve got access to StartTime, AnswerTime and FinishTime, which is awesome, and allows me to historically store phone calls in my own database.

However, I also need to track when a call is being “Originated”, and I also need to have a callback notification every time a call is created from the server and out, in addition to when an incoming call comes into the server.

After lots of debugging, testing and Googling, I’ve found somebody telling me that I should subscribe to the “NewState” event - Which I am currently doing. My problem though is that when I place a call from for instance an extension to an external number or something similar, either physically using my (soft) phone, or by invoking the “Originate” action, I get tons of NewState events, and I’m supposed to “translate” this into a single invocation to my own system, allowing me to track the following …

  1. External Call placed (possibly through invoking “Originate”)
  2. Incoming calls detected

This is necessary since I’ll need to be able to hangup calls and redirect calls from within my own system. Hence, the CDR event is for this purpose “too late” for me …

And since I get tons of these events when a call is places, and some of these events have (to be polite) “weird data associated with them”, I am struggling to say the least how to figure out exactly what is what here …

Below is an example of how it looks like when a call is “Originated”.

	NewState EVENT
	CallerIdName - 'Outbound Call'
	CallerIdNum - '555555555'
	Channel - 'SIP/Coperato_1-00000034'
	ChannelState - '6'
	ChannelStateDesc - 'Up'
	ConnectedLineName - 'NameOfOurSoftware'
	Connectedlinenum - '<unknown>'

	NewState EVENT
	CallerIdName - 'NameOfOurSoftware'
	CallerIdNum - '<unknown>'
	Channel - 'Local/555555555@dialer-0000001d;2'
	ChannelState - '6'
	ChannelStateDesc - 'Up'
	ConnectedLineName - 'Outbound Call'
	Connectedlinenum - '555555555'

	NewState EVENT
	CallerIdName - 'Outbound Call'
	CallerIdNum - '555555555'
	Channel - 'Local/555555555@dialer-0000001d;1'
	ChannelState - '6'
	ChannelStateDesc - 'Up'
	ConnectedLineName - 'NameOfMySoftware'
	Connectedlinenum - '<unknown>'

	NewState EVENT
	CallerIdName - 'Extension 111'
	CallerIdNum - '111'
	Channel - 'SIP/111-00000035'
	ChannelState - '5'
	ChannelStateDesc - 'Ringing'
	ConnectedLineName - 'Outbound Call'
	Connectedlinenum - '555555555'

	NewState EVENT
	CallerIdName - 'Extension 111'
	CallerIdNum - '111'
	Channel - 'SIP/111-00000035'
	ChannelState - '6'
	ChannelStateDesc - 'Up'
	ChannelStateDesc - 'Up'
	ConnectedLineName - 'Outbound Call'
	Connectedlinenum - '555555555'

Below is how it looks like when a call is placed from my soft phone to an external number …

	NewState EVENT
	CallerIdName - 'Extension 111'
	CallerIdNum - '111'
	Channel - 'SIP/111-00000038'
	ChannelState - '4'
	ChannelStateDesc - 'Ring'
	ChannelStateDesc - 'Ring'
	ConnectedLineName - '<unknown>'
	Connectedlinenum - '<unknown>'

	NewState EVENT
	CallerIdName - 'Outbound Call'
	CallerIdNum - '555555555'
	Channel - 'SIP/Coperato_1-00000039'
	ChannelState - '6'
	ChannelStateDesc - 'Up'
	ConnectedLineName - '<unknown>'
	Connectedlinenum - '999999999'

	NewState EVENT
	CallerIdName - '<unknown>'
	CallerIdNum - '999999999'
	Channel - 'SIP/111-00000038'
	ChannelState - '6'
	ChannelStateDesc - 'Up'
	ConnectedLineName - 'Outbound Call'
	Connectedlinenum - '555555555'

Then finally how it looks like when an incoming call is received …

	NewState EVENT
	CallerIdName - '999999999'
	CallerIdNum - '999999999'
	Channel - 'SIP/Coperato_1-00000036'
	ChannelState - '4'
	ChannelStateDesc - 'Ring'
	ConnectedLineName - '<unknown>'
	Connectedlinenum - '<unknown>'

	NewState EVENT
	CallerIdName - 'Extension 111'
	CallerIdNum - '111'
	Channel - 'SIP/111-00000037'
	ChannelState - '5'
	ChannelStateDesc - 'Ringing'
	ConnectedLineName - '999999999'
	Connectedlinenum - '999999999'

	NewState EVENT
	CallerIdName - 'Extension 111'
	CallerIdNum - '111'
	Channel - 'SIP/111-00000037'
	ChannelState - '6'
	ChannelStateDesc - 'Up'
	ConnectedLineName - '999999999'
	Connectedlinenum - '999999999'

	NewState EVENT
	CallerIdName - '999999999'
	CallerIdNum - '999999999'
	Channel - 'SIP/Coperato_1-00000036'
	ChannelState - '6'
	ChannelStateDesc - 'Up'
	ConnectedLineName - '<unknown>'
	Connectedlinenum - '<unknown>'

My problem is figuring out what is what. At the moment I am first checking the ChannelState and State attributes of these events. If it is 4 or “Up”, I check if the CallerIdNum is less in size than 6 digits (I can assume that extensions are always less than 6 digits).

Then if the state is “Up” or the ChannelState is 6, I assume an outbounding call if the CallerIdName is “Outbound Call”. However, at this point I check to see if ConnectedLineName is “NameOfMySoftware”, and if it is, I only invoke my callback if the Channel contains “@dialer”.

If the state is “Ringing” or ChannelState is “5”, and the ConnectedLineName is “Outbound Call”, I invoke my callback as an “Incoming call”.

Basically, the code is a total mess, and I am struggling with it. And although it kind of works, I suspect some of these things, such as the CallerIdName of “Outbound Call” might be configured differently in different configurations of FreePBX …

If anybody here have any advice as to how I can actually track an incoming, outgoing (including “Originated” call), immediately once it occurs, I would deeply appreciate it …

FYI - The actual phone numbers above have been changed to anonymize my data …

Alternatively, if some of you here know some resource as to where I can figure out this information, I would deeply appreciate it. The problems is arguably more about the Asterisk Management API I assume than the FreePBX distro of it. I am using AsterNET and C# (not that it matters) to implement my own system …

For those fluent in that language, you can see my actual code below …

Inside my “NewState” event handler …

/*
    * Figuring out what type of event we should raise, if any.
    */
if (e.State == "Ring" || e.ChannelState == "4") {

    /*
        * Verifying that this actually is an outgoing, at which point the CallerIdNum will be short.
        * Notice, this does not work if a client has setup extensions which are 6 or more digits,
        * which according to Erez would never happen.
        */
    if (e.CallerIdNum != null && e.CallerIdNum.Length >= 6) {

        // Raising event.
        _callback.OutgoingAsync(dialerResult);
    }

} else if (e.State == "Up" || e.ChannelState == "6") {

    // Checking that this was an outbound call.
    if (e.CallerIdName == "Outbound Call") {

        /*
            * If this was an "Originated" call, created by the module itself, then the ConnectedLineName
            * for the event will be the caller id for the app as it is placing calls, at which point a
            * NewState event with the channel created towards the dialer will be published at some point.
            *
            * To make sure we don't publish the same event twice, we only invoke OutgoingAsync callback
            * for the "@dialer" channel, which implies the channel will be the same as the channel
            * returned by "Originate".
            */
        if (e.ConnectedLineName == _callerIdForOutgoing) {

            // This is a call that was "Originated" by the module itself.
            if (e.Channel.Contains("@dialer")) {

                // Raising event.
                _callback.AnsweredAsync(dialerResult);
            }
        } else {

            // This is a call that was NOT "Originated" by the system.
            _callback.AnsweredAsync(dialerResult);
        }
    }

} else if (e.State == "Ringing" || e.ChannelState == "5") {

    /*
        * Notice, when we "Originate" a call, this piece of code will kick in, but with the
        * ConnectedLineName being "Outbound Call", at which point we should (obviously) not
        * call our duplex callback.
        */
    if (e.ConnectedLineName != "Outbound Call") {

        // Raising event.
        _callback.IncomingAsync(dialerResult);
    }
}

One thing that might help your mental picture is the understanding that Asterisk (and hence FreePBX) is a Back to Back Service agent system. All calls “originate” through a connection to the server and are served out on another channel with an “outgoing” connection to the destination. As such, all calls have two legs, which makes your tracking of the call a little more challenging.

Because of that, your events don’t give you enough information without interrogating the Channel Name. All of your calls to and from Internal LAN calls will start with “SIP/{extension}” or “LOCAL/{number}”, where the extension number matches one of your local extensions. All calls to and from external connections should be in the form of “SIP/{trunk_name}”.

There are a lot of hints in the sequence. For example, if you get an extension status of “UP”, you know the call is going to that extension, since the system doesn’t check to see if the extension is up if it’s processing a call from that extension. You were wondering about catching an inbound call early - this indication (that the system checked and the extension is up) is a good one.

Anything with “LOCAL” is coming from your application, so an “incoming” call to one of your extensions is being processed, followed by an outgoing call to your trunk.

Anything with “SIP/trunkname” is either coming from or going to your external connection. Anything with “SIP/extension” is a local call.

In my experience from working with AMI, there should be other fields that you would normally get that can tie your data stream together. For example, there should be a PID being reported, as well as the UniqID for the call recording (even if you don’t record the calls), which should remain constant, allowing you to connect these together.

Thx Dave, this made more sense to me than my current approach which basically was “shotgun debugging” and “interpreting log files” with my brain as the interpreter, trying to guess …