Skip to content

CDR & Asterisk Integration

How Asterisk stores call data and how AstraPBX processes it.

CDR table schema

Asterisk writes one row per channel to asterisk_cdr via ODBC:

Field What it stores
calldate Channel creation time
src Caller number
dst Destination as dialed
dcontext Dialplan context (key for direction classification)
channel Caller's PJSIP channel name
dstchannel Destination's PJSIP channel name
lastapp Last app executed (Dial, Queue, Playback, Hangup)
lastdata Arguments to lastapp
duration Total seconds (ring + talk)
billsec Talk seconds only (matches recording length)
disposition ANSWERED / NO ANSWER / BUSY / FAILED
accountcode Organisation UUID
uniqueid Unique channel ID
linkedid Groups all legs of one call
recordingfile MixMonitor filename
userfield HANGUPCAUSE\|CHANNEL(hangupsource)
hangup_reason Numeric hangup cause code
queue_name Queue name (set after Queue() app)
queue_wait_time Seconds caller waited in queue
answered_agent Agent endpoint that answered

CDR pipeline

1. Call ends → Asterisk writes to asterisk_cdr (ODBC, automatic)
2. AMI Cdr event → asteriskManager.js writes to call_records (app table)
3. CDR poller (every 30s) → reads new rows from asterisk_cdr
   → classifies direction (inbound/outbound/internal)
   → finds org_id from accountcode or channel name
   → POSTs inbound calls to LogsUpdate /auto-ticket/{orgId}
4. GET /api/v1/calls → reads directly from asterisk_cdr

Direction classification

channel includes 'trunk' AND src >= 7 digits     → inbound
dcontext includes 'outbound'                      → outbound
dcontext ends with '_incoming'                    → inbound (fallback)
otherwise                                         → internal

One call, multiple CDR rows

A single phone call can produce 2-4 CDR rows (one per channel leg). The API deduplicates by linkedid, keeping the longest-duration row.

Call type Rows Channel pattern
Direct (A → B) 1 PJSIP/endpoint
Queue call 2-3 Local/ channels + PJSIP
Click-to-call 2 Local/ + PJSIP/trunk
AI outbound 2 Manual insert + PJSIP/trunk auto-CDR

Hangup handler

The dialplan hangup handler (h extension) stores:

CDR(userfield) = ${HANGUPCAUSE}|${CHANNEL(hangupsource)}
CDR(hangup_reason) = ${HANGUPCAUSE}
CDR(organization_id) = ${ORG_ID}

Common hangup causes: - 16 — Normal Clearing (one party hung up) - 17 — User Busy - 18 — No User Responding (ring timeout) - 19 — No Answer - 21 — Call Rejected

Config deployment

POST /api/v1/config/deploy generates per-org Asterisk files:

  • pjsip_<orgname>.conf — PJSIP endpoints, auth, AORs for all users + trunks
  • ext_<orgname>.conf — dialplan contexts (internal, incoming, outbound, queue, IVR)
  • queues_<orgname>.conf — queue definitions and members

Files written to /etc/asterisk/ and Asterisk reloaded via AMI (core reload).

Trunk generator

Per-org trunks do NOT create IP-based identify rules. Only the system-level tata_gateway endpoint uses IP matching (10.10.10.2). This prevents collisions where org trunks steal inbound Tata calls.

Storage capacity

At 500 bytes/row: - 18K calls/day (30 clients) → 270 MB/month → 3.2 GB/year - 75 GB disk holds ~23 years of CDR data - Recordings are the bottleneck, not CDR (moved to GCS hourly)