Zum Hauptinhalt springen

Call Data Records

The CDR URL deals with the problem that the PBX needs to notify external servers about finished call legs. This is slightly different from the problem of informing about calls, as calls may go through different states. For example, when a call comes in on a trunk, goes to an auto attendant, then to a queue, it gets picked up by an agent and then is transferred without consultation to another agent. There is only one trunk CDR being written regardless of the various transitions that the whole call went through. This can be seen as the carriers perspective, who does not care much about what exactly happened inside the PBX; the only interest is to find out when the call started, when it got connected and when it got disconnected. The call records in the web interface of the system and the domain are different from that, as they display the whole call regardless of the call legs that were used inside the call object.

The CDR URL contains zero, one or more strings that tell the PBX where to send leg CDR. These strings are separated by space characters.

cdrsettings1.png

The URL must start with a scheme, then followed by a colon. The following schemes are supported:

Scheme FormatExample
webcdrThe CDR is sent by the HTTP protocol with a JSON attachment. webcdr://server.com/cdr-collector
mongodbThe CDR is sent to a MongoDB database server.mongodb://192.168.1.2:27000/database/table
json, jsonsThe CDR is sent by the HTTP protocol with a JSON attachment.jsons://server.com/collect/json.php
mailtoThe CDR is sent by email.mailto:test@domain.com
file, filet, fileto, filetiThe system appends a record to a file.file:cdr/file.txt, filet:cdr-$d.csv
cdr, trunk, trunkout, trunkinThe system sends a CDR line over a TCP connection.trunk://tcp.server.com
http, https, soap, soapsThe CDR is sent by the HTTP protocol with a XML attachment.https://server.com/cdr/collector.asp

MongoDB

MongoDB is a powerful database that can store BSON objects (see mongodb.com ). The PBX can write CDR records natively into the database. The schema for the URL is like this: mongodb://server:port/database/table. The server must be an IP address orlocalhost for the loopback address 127.0.0.1. The port defaults to 27019 which is currently the standard MongoDB port; however we recommend to explicitly specify the port. The database string tells the server where to store the CDR; this string may contain the variable {domain}which is replaced with the name of the domain. The table name is a string that tells the PBX where to store the CDR.

The feature is available from version 5.3 for the hosted PBX.

The CDR is a BSON object that contains general information about the call, an array of extension-related records, of trunk-related records, of internal state-related information and about recordings in this call. The following tables show the details of the structures.

General Information

For each call the system generates one record in the database. It has the following structure.

 FieldMeaning
 domainThe primary (canonical) name of the domain in which the call took place.
 callidThe primary Call-ID of the call. This is typically the Call-ID of the first call leg of the call. However during transfer and pickup scenarios, this can be also the Call-ID of another call leg.
 fromThe From field of the call. This is typically the one who started the call. The format is like it appears in the SIP headers, including the display name and the URI for the call. This field is mainly for descriptive purposes and should not be used to make assumptions on billing-related topics.
 toThe To field of the call, similar to the From field. This field indicates the other party of the call.
 startThe timestamp when the call was created.
 connectThe timestamp when the call was connected the first time. The call can get connected again later, for example when users use the PBX to make outbound calls from outside telephones.
 endThe timestamp when the last call leg was disconnected.
 recordingsAn array describing the recordings in this call (see below).
 trunklegsAn array describing the trunk legs in this call (see below). Legs are trunk legs when there was a trunk involved in the call.
 extensionlegsAn array describing the extension legs in this call (see below).
 statesAn array describing the states in this call (see below).

Extension Records

Legs are extension legs if the call was connected to an extension of the PBX. This is typically the case when the call was connected to a VoIP phone of WebRTC client in the user portal. Calls that are associated with an extensions cell phone are both trunk and extension legs and will have an entry in trunklegs and extensionlegs. In this case, the Call-ID of the legs will be the same.

 FieldMeaning
 callidThe Call-ID for this leg.
 fromThe value of the From header at the time when the call leg started. Note that the value may change afterwards.
 toThe value of the To header, like the value of the From header but the other direction of the call.
 directionThe direction of the call. The value of this field can be either I for inbound calls or O for outbound. The direction depends on the users experience for this call leg. For example in a transfer scenario where another user starts an outbound call and then transfers that call to an user, that user would see this as an inbound call.
 extensionThe primary account name of the extension.
 redirectThe value of the To header as it was presented to the users device. This might be different from the To header, for example because of settings in the group on how caller-ID should be presented.
 idleThe number of seconds between hanging up the last call before picking up this call leg for this extension. If the duration is longer than the maximum idle time of the domain, the value is not reported; this usually means that the agent came back in the morning.
 startA timestamp when that call leg was started.
 connectThe timestamp when that call leg was connected. If the call was not connected (e.g. only ringing), then this timestamp is not present.
 endThe timestamp when that call leg was disconnected.
 ipadrThe IP address that was perceived from the PBX for this call.
 qualityThe quality report that was calculated by the PBX for this call leg (see RFC 6035 for more information).
 typeThe call type for this call leg: m means missed call, r means received (connected) call and d means dialled, outbound call.
 cmcIf there was a client matter code set for this call leg, it will be reported in this field.
 codecThe codec was used for this call.

Trunk Records

 FieldMeaning
directionThe direction of the call , I for incoming and O for outgoing for this specific call, independent from the underlying logic of the call. For example, when forking an inbound call to the PBX to a user's cell phone, the cell phone call leg will appear as outbound call.
fromThe value of the From header at the time when the call leg started. Note that the value may change afterwards.
toThe value of the To header, like the value of the From header but the other direction of the call.
remotepartyThe remote party of the call. For outbound calls, it contains the number that is used for billing. This is often similar to the To header, but can differ for example when group calls are triggering trunk calls.
localpartyThe local party of the call.
trunkThe name of the trunk was was used for the call.
costThe cost for the call, when available. Cost can be defined in the rates table for the trunk.
startA timestamp when that call leg was started.
connectThe timestamp when that call leg was connected. If the call was not connected (e.g. only ringing), then this timestamp is not present.
endThe timestamp when that call leg was disconnected.
ipadrThe IP address that was perceived from the PBX for this call.
qualityThe quality report that was calculated by the PBX for this call leg (see RFC 6035 for more information).
cmcIf there was a client matter code set for this call leg, it will be reported in this field.
extensionIf this call was associated with an extension, for example because this was a call to a user's cell phone, the primary name of the extension is reported here.
codecThe codec was was used for this call.
mosThe calculated MOS score for the call as reported in the MOS score table of the trunk.

Internal State

Calls are going through one or more states. For example, a call might first hit an auto attendant, then gets transferred into a ring group, then after a timeout into a IVR node and then into a mailbox. The states array shows the history of those states.

 FieldMeaning
typeThe type of the call. This field can take the following values as shown below.
fromThe value of the "From" header at the time when the call leg started. Note that the value may change afterwards.
toThe value of the "To" header, like the value of the "From" header but the other direction of the call.
languageThe language that was used in this state. The language is a two-digit code, e.g. "en" for US-English and "de" for German.
startA timestamp when that state was entered.
durationivrThe duration in ms how long the call was in a IVR state, rendering annoucements to the user.
durationringThe duration how long the call was ringing a user.
durationtalkThe duration how long the call was connected to a user. This is independent from the trunk connected time. It also includes the time when the call was on hold.
durationholdThe duration how long the call was on hold in this state.
durationidleThe duration how long the extension was idle (see idle above), if there was a extension connected in the state.
reasonThe reason for terminating the state. This field can have the values below.
accountThe account that was used for the state. This is primary name of the account that was used in the state.
extensionIf there was a extension involved in the state, this field contains the name of the extension. This is the connected agent in the case of a ring group or or call queue.

The type can have the following values:

ValueMeaning
 acdWhen the call goes to a call queue.
 attendantWhen the call goes to an auto attendant this state is used. Also, when someone calls an extension directly (not through a group), this state is used.
 mailboxWhen a call hits the mailbox, it uses this state.
 extcallWhen a user places a regular external call, this state is used. It may also contain special operations, like entering a PIN code for authentication purposes.
 huntThis state is used for ring group calls.
 conferenceIn a conference call, this state is used.
 orbitWhen the call is in a park orbit, it uses the orbit state.
 hootThe type hoot is used during paging.
 srvflagThis state means that the service flag was called (in order to change it).
 ivrnodeThis state is used when processing IVR nodes.
 callerThis state is used when the PBX initiates calls, for example when inviting participants for conferences or waking up hotel guests.
 starcodeThis state is used when processing starcodes, e.g. DND or call redirection.

The reason field can have the following values:

ValueMeaning
 hcThe call was connected in the queue and cleared normal.
 hwThe call was disconnected by the user while waiting in the queue.
 hrThe call was disconnected by the user while ringing in the queue.
 hbThe call was disconnected by the user while the queue was sending a busy signal.
 rwThe state was terminated because of wait timeout in the queue.
 rrThe state ended because the call was ringing too long in the queue.
 raThe call was redirected because it was an anonymous call in the queue.
 userThe user pressed a DTMF key that triggered an action in the queue.
 soapA external server response triggered a redirection or termination in the queue.
 missedThe call was missed in the auto attendant.

Recording Records

During a call, zero, one or more recordings can be triggered. For example because the trunk setting triggered a recording, or because the queue was set to record the call. Recordings can cover a part of the call or the whole call, depending on who triggered the recording.

 FieldMeaning
timeThe timestamp for when the recording started.
fileThe location of the file in the file system.
accountThe account that triggered the recording.
extensionThe extension that was involved in the recording.

webcdr

JSON has become a popular data format because it can be easily parsed in JavaScript and many other script language. The PBX generates an object that contains a number of variables. This method is gives a JSON cdr that is easier to parse and make sense of than the previous json method described below, which is still supported for backward compatibility.

URL

The URL for sending out JSON CDR consists of a scheme which should be webcdr in this case, the server address, an optional port number and an optional resource.

The PBX will send a CDR record that contains relevant information for the call, including a field for trunklegs and another one for extensionlegs for extra leg details. Because the CDR will be processed by a script anyway, that script can easily take care about filtering the relevant records out. For example, the script can check if the record contains a trunk and is outbound, and if not discard the record.

The resource can be used to point the server to the right file, typical examples are /cdr or /json/cdr. If not provided the resource will default to /.

Format

The JSON object consists of several entries:

 FieldTypeMeaning
systemTextThe activation code for the system. This code can be used to identify the PBX system.
domainTextThe domain for the call object.
callidTextThis is the Call-ID for the call. This Call-ID should be unique.
fromTextThe originator of the call. The format is according to the SIP standard with an optional display name and a SIP URI. The real destination may be different. This field shows what the original call headers were when the call was set up.
toTextThe destination of the call. The format is the same like the From header.
startDate/TimeThe time when the call was started in GMT.
connectDate/TimeThe time when the call was connected in GMT. This field may be empty if the call was not connected.
endDate/TimeThe time when the call ended in GMT.
recordingsJSON ObjectIf the call was recorded automatically, this field contains the location where the call was recorded.
runklegsJSON ArrayDetails of the trunk legs as a json array.
extensionlegsJSON ArrayDetails of the extension legs as a json array.

PHP Example Code

The following code can be used as an example for storing the CDR in a PHP/MySQL environment.

<?php
// Check where this request is coming from:
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="CDR Reporting"');
header('HTTP/1.0 401 Unauthorized');
echo 'Please enter a username and a password';
exit;
}


// Connect to the database:
$report_host = "localhost";
$report_user = "root";
$report_pass = "pbxnsip";
$report_name = "cdr";

$mysqli = new mysqli($report_host, $report_user, $report_pass, $report_name);
if($mysqli->connect_errno) exit("Connect failed: " . $mysqli->connect_error);

// Check if the user exists in the database:
$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
$result = $mysqli->query("SELECT id FROM system WHERE username='" .
$mysqli->real_escape_string($username) .
"' AND password='" .
$mysqli->real_escape_string($password) .
"'");
if($result == FALSE || $result->num_rows != 1) exit("User not found or password");
$row = $result->fetch_assoc();
$system = $row["id"];

// Parse a SIP address
// "UTF-8" <sip:number@domain>
// UTF-8 <sip:number@domain>
// <sip:number@domain>
// sip:number@domain
function parse_sip_address($adr, &$name, &$number) {
// Try if this contains a name:
if(preg_match('/"(.*)" *<sip:(.*)@.*>/', $adr, $matches) > 0) {
$name = $matches[1];
$number = $matches[2];
return;
}
if(preg_match('/(.*) *<sip:(.*)@.*>/', $adr, $matches) > 0) {
$name = $matches[1];
$number = $matches[2];
return;
}
if(preg_match('<sip:(.*)@.*>/', $adr, $matches) > 0) {
$name = "";
$number = $matches[1];
return;
}
if(preg_match('sip:(.*)@.*/', $adr, $matches) > 0) {
$name = "";
$number = $matches[1];
return;
}

// Can't figure it out:
$name = "";
$number = "";
}

// Take a number
function parse_date($date) {
return date('Y-m-d H:i:s', $date);
}

// Check if this comes in JSON form:
if(isset($_SERVER["CONTENT_TYPE"]) && $_SERVER["CONTENT_TYPE"] == "application/json") {
$json = json_decode(file_get_contents("php://input"));

parse_sip_address($json->from, $fname, $fnumber);
parse_sip_address($json->to, $tname, $tnumber);

// Add the row:
$query = sprintf("INSERT INTO cdr (system,callid,fnumber,tnumber,fname,tname,start,connect,end) values ('%s','%s','%s','%s','%s','%s','%s','%s','%s')",
$mysqli->real_escape_string($system),
$mysqli->real_escape_string($json->callid),
$mysqli->real_escape_string($fnumber),
$mysqli->real_escape_string($tnumber),
$mysqli->real_escape_string($fname),
$mysqli->real_escape_string($tname),
$mysqli->real_escape_string(parse_date($json->start)),
$mysqli->real_escape_string(parse_date($json->connect)),
$mysqli->real_escape_string(parse_date($json->end)));
$mysqli->query($query);
$id = $mysqli->insert_id;

$reqrec = array();
for($i = 0; $i < count($json->recordings); $i++) {
$rec = $json->recordings[$i];
$filename = sprintf("rec-%d-%d.wav", $id, $i + 1);
$reqrec[$filename] = $rec->file;
$query = sprintf("INSERT INTO rec (cdr,start,file,account,extension) values ('%d','%s','%s','%s','%s')",
$id,
$mysqli->real_escape_string(parse_date($rec->time)),
$mysqli->real_escape_string($filename),
$mysqli->real_escape_string($rec->account),
$mysqli->real_escape_string($rec->extension));
$mysqli->query($query);
}

// Tell the PBX which recordings need to be uploaded:
$myadr = sprintf("%s://%s:%d%s", $_SERVER['HTTPS'] ? "https" : "http", $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $_SERVER['REQUEST_URI']);
$json = '{"upload":[';
$first = TRUE;
foreach($reqrec as $i => $v) {
if(!$first) { $json .= ','; $first = FALSE; }
$json .= sprintf('{"file": "%s", "url": "%s/%s"}', $v, $myadr, $i);
}
$json .= ']}';
echo $json;
}

// Here you can set the table up. Make sure that you CHANGE THE PASSWORD.
else if(isset($_GET["action"]) && $_GET["action"] == "setup" && $_GET["password"] == "secret") {
}

?>

json, jsons

JSON has become a popular data format because it can be easily parsed in JavaScript and many other script language. The PBX generates an object that contains a number of variables. The variable action is set in that object to call-data-record, so that the receiving end can easily figure out that this is a CDR record.

JSON CDR are supported in version 5.0.8 and higher.

URL

The URL for sending out JSON CDR consists of a scheme, the server address, an optional port number and an optional resource. The URL is similar to HTTP URL known from browsing the internet. The scheme can be either json or jsons. json uses HTTP, while jsons uses HTTPS transport layer.

The PBX will send a CDR record for all call legs. Because the CDR will be processed by a script anyway, that script can easily take care about filtering the relevant records out. For example, the script can check if the record contains a trunk and is outbound, and if not discard the record.

The server address can be either an IP address or a DNS address. IP addresses can be IPv4 addresses (e.g. 192.168.1.2) or IPv6 addresses (e.g. [db8:32::4f3:435]). DNS addresses must have either DNA A or DNS AAAA records available. If the port number is not provided, it defaults to 80 for HTTP and 443 for HTTPS.

The resource can be used to point the server to the right file, typical examples are /cdr or /json/cdr. If not provided the resource will default to /.

Format

The JSON object consists of several entries:

 EntryTypeDescription
actionTextThis entry has always the value call-data-record.
SystemTextThe activation code for the system. This code can be used to identify the PBX system.
PrimaryCallIDTextThe Call-ID for the call object. There may be several legs that reference this Call-ID. This is a way to link them together.
CallIDTextThis is the Call-ID for the call leg. This Call-ID should be unique.
FromTextThe originator of the call. The format is according to the SIP standard with an optional display name and a SIP URI. The real destination may be different. This field shows what the original call headers were when the call was set up.
ToText The destination of the call. The format is the same like the From header.
DirectionTextThe direction of the call. The direction can be I (inbound) or O (outbound).
RemotePartyTextThe remote party for this call leg from the PBX point of view. This format for this field is the number only (not in SIP header style).
LocalPartyTextThe local party for this call leg from the PBX point of view; usually an extension number. This format for this field is the number only (not in SIP header style).
TrunkNameTextThe name of the trunk that was used for this call, if this call leg was on a trunk.
TrunkIDIntegerThe number of the trunk on the system. While trunk names don't necessarily have to be unique, the TrunkID is.
CostTextThe cost for this call, if available. In order to use this element, the trunk must have a rate table set up.
CMCTextThe client matter code (customer number) for the call, if available.
DomainTextThe name of the domain in which the call was made.
TimeStartDate/TimeThe time when the call was started in GMT.
TimeConnectedDate/TimeThe time when the call was connected in GMT. This field may be empty if the call was not connected.
TimeEndDate/TimeThe time when the call ended in GMT.
LocalTimeDate/TimeThe local time of the domain when the call was connected.
DurationHHMMSSTextDuration of the call in the format HHMMSS (hour, minute, second).
DurationINTThe duration of the call in seconds.
RecordLocationTextIf the call was recorded automatically, this field contains the location where the call was recorded.
RecordingIdList of INTIf the call was recorded automatically, this field contains the list of ID for the recordings. For example, they can be retrieved with the REST API command /rest/domain/domain/recs?id=xxx.
RecordUserList of INTIf the call was recorded automatically, this field contains the list of ID for the users that have recorded the calls.
TypeTextThe type of the call. For example, when the call went to voicemail, the type is `mailbox.
ExtensionTextIf this call leg was connected to an extension, this field contains the canonical name of the extension. Note that for calls that went to a user's cell phone, both the extension and the trunk information will be available.
IdleDurationINTThe duration how long the extension was idle before connecting the call.
RingDurationINTThe duration how long the call was in ringing state before connecting the call.
HoldDurationINTThe duration how long the call was on hold.
IvrDurationINTThe duration how long the call was in IVR state before connecting the call. This entry is important in ACD calls, where callers are waiting before the call starts ringing.
AccountNumberTextThe accounts number for the call, if available. The meaning of the field depends on the type of the call. For example, for ACD calls this field will contain the number of the ACD.
IPAdrTextThe IP address of the remote site.
QualityTextThe call quality summary for the call.

PHP Example Code

The following code can be used as an example for storing the CDR in a PHP/MySQL environment.

<?php

// That needs to be set up accordingly:
$report_host = "localhost";
$report_name = "cdrdb";
$report_pass = "password";
$report_user = "root";

// Check if this comes in JSON form:
if(isset($_SERVER["CONTENT_TYPE"]) && $_SERVER["CONTENT_TYPE"] == "application/json") {
$mysqli = new mysqli($report_host, $report_user, $report_pass, $report_name);
if($mysqli->connect_errno) exit("Connect failed: " . $mysqli->connect_error);

$json = json_decode(file_get_contents("php://input"));
if($json->action == "call-data-record") {
// Check if the request comes from the expected IP address:
$system = $mysqli->real_escape_string($json->System);
$remote = $_SERVER["REMOTE_ADDR"];
$result = $mysqli->query("SELECT * FROM system WHERE code='$system' AND adr='$remote'");
if($result == FALSE || $result->num_rows != 1) exit("System not found or request from wrong IP address");

// Add the row:
$query = sprintf("INSERT INTO cdr (sys,primarycallid,callid,cid_from,cid_to,direction,remoteparty,localparty,trunkname,trunkid,cost,cmc,domain,timestart,timeconnected,timeend,ltime,durationhhmmss,duration,recordlocation,type,extension,idleduration,ringduration,holdduration,ivrduration,accountnumber,ipadr) values ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')",
$mysqli->real_escape_string($json->System),
$mysqli->real_escape_string($json->PrimaryCallID),
$mysqli->real_escape_string($json->CallID),
$mysqli->real_escape_string($json->From),
$mysqli->real_escape_string($json->To),
$mysqli->real_escape_string($json->Direction),
$mysqli->real_escape_string($json->RemoteParty),
$mysqli->real_escape_string($json->LocalParty),
$mysqli->real_escape_string($json->TrunkName),
$mysqli->real_escape_string($json->TrunkID),
$mysqli->real_escape_string($json->Cost),
$mysqli->real_escape_string($json->CMC),
$mysqli->real_escape_string($json->Domain),
$mysqli->real_escape_string($json->TimeStart),
$mysqli->real_escape_string($json->TimeConnected),
$mysqli->real_escape_string($json->TimeEnd),
$mysqli->real_escape_string($json->LocalTime),
$mysqli->real_escape_string($json->DurationHHMMSS),
$mysqli->real_escape_string($json->Duration),
$mysqli->real_escape_string($json->RecordLocation),
$mysqli->real_escape_string($json->Type),
$mysqli->real_escape_string($json->Extension),
$mysqli->real_escape_string($json->IdleDuration),
$mysqli->real_escape_string($json->RingDuration),
$mysqli->real_escape_string($json->HoldDuration),
$mysqli->real_escape_string($json->IvrDuration),
$mysqli->real_escape_string($json->AccountNumbery),
$mysqli->real_escape_string($json->IPAdr));
$mysqli->query($query);
}
}

// Here is some code to set the table up. Make sure that you CHANGE THE PASSWORD.
else if(isset($_GET["action"]) && $_GET["action"] == "setup" && $_GET["password"] == "secret") {
$mysqli = new mysqli($report_host, $report_user, $report_pass, $report_name);
if($mysqli->connect_errno) exit("Connect failed: " . $mysqli->connect_error);
$query = sprintf("CREATE TABLE cdr (" .
"id INT NOT NULL AUTO_INCREMENT PRIMARY KEY," .
"sys VARCHAR(16)," .
"primarycallid VARCHAR(255)," .
"callid VARCHAR(255)," .
"cid_from VARCHAR(255)," .
"cid_to VARCHAR(255)," .
"direction VARCHAR(4)," . // I or O
"remoteparty VARCHAR(255)," .
"localparty VARCHAR(255)," .
"trunkname VARCHAR(255)," .
"trunkid INT," .
"cost VARCHAR(10)," .
"cmc VARCHAR(20)," .
"domain VARCHAR(255)," .
"timestart DATETIME," .
"timeconnected DATETIME," .
"timeend DATETIME," .
"ltime DATETIME," .
"durationhhmmss VARCHAR(10)," .
"duration INT," .
"recordlocation VARCHAR(255)," .
"type VARCHAR(10)," .
"extension VARCHAR(255)," .
"idleduration INT," .
"ringduration INT," .
"holdduration INT," .
"ivrduration INT," .
"accountnumber VARCHAR(20)," . // Assuming extensions are never longer than 20 characters
"ipadr VARCHAR(40))"); // Enough space for IPv6
$mysqli->query($query);
printf($query);
}
?>

mailto

CDR can also be send by email. Sending out emails has the advantage that the sending procedure is very reliable, even if the system should be disconnected to the outside world for a longer time. Also, archiving emails is easy, especially when email accounts are available for free with a lot of available space. Because the CDR URL string may contain several destinations, this may help as a secondary backup CDR storage.

The email will contain the values from the other CDR record types, listed in a line-based format. The fields are From, To, Primary-CID, Call-ID, Direction, Type, Remote, Local, TrunkName, TrunkID, Domain, LocalTime, Start, Connect, DurationHHMMSS, DurationSec, End, AccountNumber, Cost, IPAdr, RecordLocation, RecordUsers and CMC.

file, filet, fileto, fileti

Writing CDR to a file on the file system is a simple, common way to keep track on the activity of the PBX. Please note that leading forward slashes are removed from the file name.

 SchemeMeaning
fileAll CDR are written. This includes CDR for internal calls. A CDR is written for each call leg, resulting in multiple CDR for each call.
filetA record is written only if a trunk is involved.
filetoA record is written only if a trunk is involved and the call was outbound on that trunk.
filetiA record is written only if a trunk is involved and the call was inbound on that trunk.

The location of the CDR can be set with the value behind the scheme. The following patterns are automatically expanded before opening the file:

 ShortDescription
$$A dollar sign $
$cReplaced with the value cdr (legacy).
$mThe name of the domain. This makes it easy to keep different files for each domain.
$dThe date in the 8-digit format YYYYMMDD.
$hThe hour in 2-digit format HH.

The system automatically appends for each CDR a new line to the resulting file. The line contains the following information:

  • The domain name
  • The primary call-ID of the call
  • The leg call-ID
  • The name of the one who started the call (From)
  • The name of the one who received the call (To)
  • The direction of the call
  • The type of the call, for example attendant or hunt
  • The start timestamp in the format YYYYMMDDHHMMSS
  • The timestamp when the call got connected in the format YYYYMMDDHHMMSS, if the call leg was connected at all
  • The timestamp when the call got disconnected in the format YYYYMMDDHHMMSS
  • The name of the trunk
  • The local party
  • The remote party
  • The recording location
  • The client matter code, if present
  • The IP address of the connected party
  • The call quality report, if it has been enabled
  • The extension associated with the call, for example the agent in the call queue
  • The account that was in use (e.g. auto attendant or ring group) when the call leg ended

Alternatively, the format can be specified using the variables from the cdr format below.

cdr, trunk, trunkout, trunkin

There are several commercial tools available that collect CDR information through simple TCP-based communication. Typically these tools emulate the behavior of old RS-232 based systems in a network world.

The different schemes are filtering the content that is being sent to the CDR recipient.

 SchemeMeaning
cdrAll CDR are sent. This includes CDR for internal calls. A CDR is sent for each call leg, resulting in multiple CDR for each call.
trunkA record is sent only if a trunk is involved.
trunkoutA record is sent only if a trunk is involved and the call was outbound on that trunk.
trunkinA record is sent only if a trunk is involved and the call was inbound on that trunk.

Each CDR is sent in one line of ASCII text terminated by a CRLF pair over a TCP-based communication link. In order to send the simple CDR to an external server, you need to specify the IP address and the port for the server. You do this in the SOAP CDR setting on the admin level of the PBX. In order to differentiate the destination from a SOAP CDR, you must use one of the schemes in front of the IP address and the port. The fields are separated by a colon. For example, cdr:192.168.1.2:10000 would send the CDR to the IP address 192.168.1.2 on port 10000.

You can define a format string that the PBX uses to generate the CDR line. You can use any characters in this string; however the dollar sign has a special meaning. The format can be defined in the global setting with the name "Format template for simple CDR strings". Starting with version 58 you can also add the string as third parameter to the entry, e.g. cdr:192.168.1.2:10000:$w$5g$10c$5d, which will then override the global setting. When sending plain text CDR from the PBX, you can use the following fields (as of version 68.0.24):

ShortLongDescriptionExample
$$$$A dollar sign$
$i$(call_id)Caller-ID of the call leg, as seen in the SIP packet. Note that a call usually has several legs with different call-ID on their legs.
$v$(call_type)Type of call at the time when the call leg ended.attendant
$m$(domain)Domain name of the call leg.domain.com
$l$(lang)Language of the call at the time when the call-leg was sent.en
$f$(sip_from)The From header, as seen in SIP packet."Alice" <sip:123@domain.com;user=phone>
$t$(sip_to)The To header, as seen in the SIP packet."Bert" <sip:456@domain.com;user=phone>
$F$(calling)The phone number of the caller, taken the From header.123
$T$(called)The phone number of the caller, taken the To header.456
$I$(ivr_user)The account that was used for the call when the leg CDR was generated. Typically this is an auto attendant, ring group or call queue.73
$(ivr_user_name)The name of account that was used for the call when the leg CDR was generated. Typically this is an auto attendant, ring group or call queue.Group 1
$I$(ivr_ext)The extension that took the call, typically when a group was called.123
$(ivr_ext_name)The name of the extension that took the call.Alice
$x$(trunk)Name of trunk of the call leg Trunk 1
$R$(account)Account that is charged for redirected call, including the domain name45@localhost
$g$(charge)Account that is charged for redirected call, without the domain name45
$g$(charge_name)The name of the account that is charged for redirected callAlice
$r$(redirect_dest)Destination for a redirected call (used only if call is redirected)
$S$(start_time)Start time in seconds since 1970
$C$(talk_duration)Connected duration of the call
$A$(ring_duration)Answered duration of call (useful if the call is ringing/answered through queue)
$E$(hold_duration)Hold duration for the call
$W$(wait_duration)IVR duration for the call
$w$(start_date_time)Start time (YYYYMMDDHHMMSS)20071023123224
$b$(date)Date (YYYYMMDD format)20071023
$B$(time)Time (HHMMSS format)142321
$e$(extension)Extension number of the call leg. Note that this is not necessarily the extension that was charged for the call (see g parameter).45
$e$(extension_name)The name of the extension number of the call leg.Alice
$o$(direction)Direction of call (i for inbound; o for out)i or o
$c$(remote_call_id)Caller-ID of remote party
$d$(duration)Duration of call in seconds (include hold time)
$s$(extn_duration)Speaking duration (does not include hold time)
$M$(cmc)Client matter code (CMC)781299
$(record_location)The location of the recorded files for the call leg, if present.
$(primary_call_id)The primary call-ID for the call. Several call legs that are part of the same call with have the same primary call-ID.

You can put the length of the string between the dollar sign and the character. For example the expression $10cwill insert a 10-digit caller-ID into the string. An example would look like $w$5g$12c$5d. It would generate line that could look like this: "20071223123224 123 9781234567 120".

http, https, soap, soaps

The PBX also supports sending CDR information in XML format. This feature was mainly kept for backward compatibilty. For new implementations we recommend to use the JSON-based format instead. The data interchange is done by using the HTTP/SOAP protocol. The CDR is encoded in a XML format and looks like this. The real file will not have indentation; here it is only for illustration purposes.

POST /cdr.xml HTTP/1.1
Host: pbx.com
SOAPAction: CDR
Content-Type: text/xml
Content-Length: 123

<env:Envelope
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:sns="http://www.vodia.com/soap/pbx">
<env:Body>
<sns:CDR>
<From>Fred Feuerstein <sip:ff@test.com></From>
<To>Tom Test <sip:tt@test.com></To>
<Type>extcall</Type>
<TimeStart>464645649</TimeStart>
<TimeConnected>464645655</TimeConnected>
<TimeEnd>464645676</TimeEnd>
<CallID>8c72b11bcd4@192.168.2.44</CallID>
<Domain>test.com</Domain>
</sns:CDR>
</env:Body>
</env:Envelope>
<pre>

The XML schema for the CDR is defined by the following XML:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns="http://tempuri.org/XMLSchema.xsd" elementFormDefault="qualified" targetNamespace="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="CDR">
<xs:complexType>
<xs:attribute name="CallID" type="xs:string" />
<xs:attribute name="Type">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="attendant" />
<xs:enumeration value="conference" />
<xs:enumeration value="extension" />
<xs:enumeration value="ivrnode" />
<xs:enumeration value="mailbox" />
<xs:enumeration value="acd" />
<xs:enumeration value="extcall" />
<xs:enumeration value="starcode" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="Domain" type="xs:string" />
<xs:attribute default="en" name="language">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="en" />
<xs:enumeration value="fr" />
<xs:enumeration value="de" />
<xs:enumeration value="sp" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="From" type="xs:string" />
<xs:attribute name="To" type="xs:string" />
<xs:attribute name="FromTrunk" type="xs:string" />
<xs:attribute name="ToTrunk" type="xs:string" />
<xs:attribute name="FromUser" type="xs:string" />
<xs:attribute name="ToUser" type="xs:string" />
<xs:attribute name="IVRUser" type="xs:string" />
<xs:attribute name="ChargeAccount" type="xs:string" />
<xs:attribute name="ChargeNumber" type="xs:string" />
<xs:attribute name="TimeStart" type="xs:unsignedLong" />
<xs:attribute name="TimeConnected" type="xs:unsignedLong" />
<xs:attribute name="TimeAnswered" type="xs:unsignedLong" />
<xs:attribute name="TimeEnd" type="xs:unsignedLong" />
<xs:attribute name="LocalTime" type="xs:date" />
<xs:attribute name="Duration" type="xs:string" />
<xs:attribute name="Extension" type="xs:string" />
<xs:attribute name="Number" type="xs:string" />
<xs:attribute name="StatisticsForward" type="xs:string" />
<xs:attribute name="StatisticsReverse" type="xs:string" />
</xs:complexType>
</xs:element>
</schema>

The format follows the SOAP specification. In the pbxnsip namespace, the record CDR indicates that a CDR shall be transmitted. The CDR may have the following attributes:

FieldMeaning
CallIDThe Call-ID for the call. This makes it easier to match the call (like a session-cookie).
TypeThe PBX reports the last type of the call. This way, it is for example possible to tell if the call went to the voicemail box.
DomainThe domain in which the call was being run.
LanguageIf a specific language was selected, this tag will indicate the language.
FromThe value of the From header as it appeared in the call.
ToSimilar to the From but for the To header.
FromUserIf the call was coming from a known extension, the extension name is reported here.
ToUserIf the call was going to a known extension, the extension name is reported here.
FromUserIf the call was coming from a known trunk, the trunk name is reported here.
ToUserIf the call was going to a known trunk, the trunk name is reported here.
ChargeAccount and ChargeNumberIf the call was redirected to an external number, the charged account and the destination number are reported here.
TimeStartThe time when the call started. All timestamps are in number of seconds since 1970 (UNIX timestamp).
TimeConnectedIf the call was connected, this stamp indicates the connection time.
TimeEndThe time when the call was disconnected or cancelled.