phptwilioasteriskdialplan

Need help getting Twilio X-Twilio-CallSid or X-Twilio-RecordingSid on outbound calls


We are logging calls along with Twilio's recording Sids to a database and since Asterisk only exposes Twilio's sip-headers to the dialplan on requests (as opposed to responses) we cannot get the headers X-Twilio-CallSid and X-Twilio-RecordingSid on the far end channel.

On a call that was originated by a local extension to Twilio and hung up by the originating extension where the BYE message comes from us locally, all Asterisk ever sees is SIP responses.

Can anybody help get the CallSid or the RecordingSid accessible via the dialplan or PHP AGI script?

We tried putting in a gosub routine on the called channel (far-end) which allowed us to access variables on that channel, but the SIP headers available are only from Asterisk's INVITE. None of the headers from Twilio responses are ever exposed. UNLESS Twilio sends a BYE request, in which case the hang up handler WILL expose the CallSid and the RecordingSid. However, if Asterisk hangs up the call, the BYE message generated by Asterisk fires the hang up handler before the required Twilio SIP response headers are received by asterisk.

PHP AGI DialScript with gosub routine.

$agi->exec("Dial", "SIP/twilio-out/+1$call->dnid,180,rU(far-channel-hangup-hook,".$call->calllog_listid.")"); 

Dial-Plan setup (extensions.conf)

[far-channel-hangup-hook]
exten => s,1,Set(calllog_listid=${ARG1})
exten => s,n,Set(CHANNEL(hangup_handler_push)=far-channel-hangup,s,1)
exten => s,n,Return()

[far-channel-hangup]
exten => _.!,1,AGI(log_sid.php)
exten => _.!,n,Return()

Solution

  • This issue is tricky to solve. On calls you originate, Twilio provides the CallSid first in the 200 OK response to your initial INVITE. Since it is only a response, Asterisk will not allow you to access the SIP header via SIP_HEADER().

    You're right, if Asterisk originates a call, you can install a hangup-handler that will fire when the call is hung up and if Twilio sends the BYE, you will have full access to the "X-Twilio" headers in your handler, but NOT if Asterisk terminates the call with its own BYE. However, if you can coax the Twilio-CallSid out of Asterisk sometime in the life of the dialog, you can use cURL in your hangup-handler to fetch more information about the recorded call based on the CallSid.

    Twilio documentation explains that you can send an HTTP GET request with the CallSid to their Recordings API and fetch the information about the recording.

    curl -X GET -https://video.twilio.com/v1/Recordings/RMXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' -u ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:your_auth_token
    

    In your PHPAGI code, you could do something like this:

        $recording_object = NULL;
        $accountsid = "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
        $auth_token = "xxxxxxxxxxxxxxxxxxx"; // Your API access auth token
        $cidurl = "https://api.twilio.com/2010-04-01/Accounts/$accountsid/Calls/$CallSid/Recordings.json";
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $cidurl);
        curl_setopt($ch, CURLOPT_USERPWD, "$accountsid:$auth_token");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $retval = curl_exec($ch);
        $obj = json_decode($retval);
        curl_close($ch);
        if ($obj) {
            if (property_exists($obj, "recordings")) {
                $recording_object = $obj->recordings;
            }
        }
    

    And you will be able to access the RecordingSid and RecordingDuration via:

        $recording_object->sid
    

    and

        $recording_object->duration
    

    (If $recording_object is not NULL)

    Now, to actually GET the X-Twilio-CallSid earlier in the call (SIP dialog) is where even more trickiness came in.

    I always build Asterisk from source. It's very easy. There are even scripts to install the dependencies for you based on your Linux Distro's repository.

    Here is an example for CentOS.

    Here is what I've done to solve this issue. There are two SIP channel drivers that come with Asterisk these days "chan_sip" and "chan_pjsip". I use chan_sip, the older (mature, tried and true) channel driver. It works well.

    If you download the source code for Asterisk and before you follow the steps in the above-referenced guide, find the file "chan_sip.c". It is usually in the directory /channels

    Edit the file.

    Inside the file, there is a function called handle_response().

    /*! \brief Handle SIP response in dialogue
        \note only called by handle_incoming */
    static void handle_response(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno)
    

    Search for that function.

    Inside that function, search for the first switch statement, and then follow the code to where your have a "case 200:" condition.

    It should look similar to the following:

        case 200:   /* 200 OK */ 
            p->authtries = 0;   /* Reset authentication counter */
            if (sipmethod == SIP_INVITE) {
                handle_response_invite(p, resp, rest, req, seqno);
            } else if (sipmethod == SIP_REGISTER) {
                handle_response_register(p, resp, rest, req, seqno);
            } else if (sipmethod == SIP_SUBSCRIBE) {
                ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
                handle_response_subscribe(p, resp, rest, req, seqno);
            } else if (sipmethod == SIP_BYE) {      /* Ok, we're ready to go */
                pvt_set_needdestroy(p, "received 200 response");
                ast_clear_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
            }
            break;
    

    Change it to look like this:

        case 200:   /* 200 OK */ 
            p->authtries = 0;   /* Reset authentication counter */
            if (sipmethod == SIP_INVITE) {
                const char *twilio_callsid = sip_get_header(req, "X-Twilio-CallSid");
                if (twilio_callsid) {
                    ast_verb(1, "** Setting channel variable 'twiliocallsid' to '%s'\n", twilio_callsid);
                    pbx_builtin_setvar_helper(owner, "twiliocallsid", twilio_callsid);
                }
                handle_response_invite(p, resp, rest, req, seqno);
            } else if (sipmethod == SIP_REGISTER) {
                handle_response_register(p, resp, rest, req, seqno);
            } else if (sipmethod == SIP_SUBSCRIBE) {
                ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
                handle_response_subscribe(p, resp, rest, req, seqno);
            } else if (sipmethod == SIP_BYE) {      /* Ok, we're ready to go */
                pvt_set_needdestroy(p, "received 200 response");
                ast_clear_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
            }
            break;
    

    Save the file. Then follow the steps to build and install Asterisk.

    Now, as soon as Twilio answers your INVITE, and sends a 200 OK which will contain the X-Twilio-CallSid chan_sip will set it as a channel variable called "twiliocallsid" and it will be accessible from the dial plan OR your PHPAGI script. You can get the CallSid, and use that in your cURL query to Twilio to get the recording information and save it however you wish.

    I'm not totally sure about this but if you have already installed Asterisk, you might still be able to shut it down, go to the directory with the source and run "make install". Someone else may have input on this, but when you build and install it from source, that's all it takes. You can modify the source as you need and complile what's been modified with "make install".

    Then stop, restart Asterisk and you're good to go!