Zum Hauptinhalt springen

CDR Streaming

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
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

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.