VitalPBX 4.5.1-6
Asterisk: 20.15.0
The problem:
Unanswered outbound calls generate a duplicate CDR — one real record from Dial(), one ghost from the hangup handler. On our system this inflates call stats by 23.5% (1,468 ghost CDRs out of 6,254 outbound in April 2026).
Root cause: endbeforehexten = yes closes the real CDR before exten => h runs, but the hangup handler ends with Hangup() which opens a new empty CDR.
Ghost CDR signature: lastapp = Hangup, duration = 0, billsec = 0, dstchannel empty, same uniqueid as the real record.
Reproduce
- Make an outbound call, don’t answer it
- Check CDR — two records, same
uniqueid
SELECT uniqueid, COUNT(*) AS cnt,
GROUP_CONCAT(lastapp ORDER BY sequence) AS apps,
GROUP_CONCAT(duration ORDER BY sequence) AS durations
FROM asterisk.cdr
WHERE calltype = '3'
AND calldate >= CURDATE() - INTERVAL 7 DAY
GROUP BY uniqueid
HAVING cnt > 1;
Fix
Add NoCDR() as the first line of exten => h in the trunk dial template. The real CDR is already saved, so nothing is lost:
exten => h,1,NoCDR()
same => n,System(test -e ${REC_FILENAME})
...
Would also be nice to have endbeforehexten exposed in the GUI or defaulted to no.
Current workaround
Using the end-trunk-dialing-hook context per tenant in extensions__90-custom.conf:
[T78_end-trunk-dialing-hook]
exten => s,1,NoCDR()
same => n,Return()
Works, but has to be repeated for every tenant prefix.
Cleanup query
DELETE FROM asterisk.cdr
WHERE calltype = '3'
AND lastapp = 'Hangup'
AND duration = 0
AND dstchannel = '';