Outbound DIALSTATUS Handling

Call Handling Asterisk 18+ -- Last reviewed 2026-03-29 outbound dialstatus error-handling gosub hangupcause q850 Found this useful? Upvote it. ×

The Problem

The default Asterisk dialplan pattern for outbound call failures is to play "all circuits are busy now" for any failure. This is misleading, a bad phone number is not a trunk failure, but the user hears the same message for both.

DIALSTATUS vs HANGUPCAUSE

After Dial() returns, Asterisk sets two variables:

DIALSTATUS alone cannot distinguish between "the number you dialed does not exist" and "the trunk is down." Both can return CONGESTION or CHANUNAVAIL. Checking HANGUPCAUSE gives you the actual network-level reason.

Common Q.850 Cause Codes

Code Name Meaning
1 UNALLOCATED_NUMBER Number does not exist
16 NORMAL_CLEARING Normal call completion
17 USER_BUSY Destination busy
19 NO_USER_RESPONSE No answer
28 INVALID_NUMBER_FORMAT Bad number format
34 NO_CIRCUIT_AVAILABLE Actual network congestion
38 NETWORK_OUT_OF_ORDER Trunk/network failure
127 INTERWORKING Unspecified interworking error

The Fix

Outbound Status Subroutine

Call this after every Dial() to the trunk:

[outbound-status]
; Answered calls - normal completion
exten => ANSWER,1,NoOp(Call answered normally)
 same => n,Return()

; Busy signal
exten => BUSY,1,NoOp(Destination busy)
 same => n,Playback(number-busy)
 same => n,Return()

; No answer
exten => NOANSWER,1,NoOp(No answer)
 same => n,Return()

; Caller cancelled
exten => CANCEL,1,NoOp(Caller cancelled)
 same => n,Return()

; Congestion - check cause code for specific failure reason
exten => CONGESTION,1,NoOp(Congestion - HANGUPCAUSE=${HANGUPCAUSE})
 same => n,GotoIf($["${HANGUPCAUSE}" = "1"]?unallocated)
 same => n,GotoIf($["${HANGUPCAUSE}" = "28"]?invalid_number)
 same => n,Playback(all-circuits-busy-now)
 same => n,Playback(pls-try-call-later)
 same => n,Return()
 same => n(unallocated),Playback(ss-noservice)
 same => n,Return()
 same => n(invalid_number),Playback(ss-noservice)
 same => n,Return()

; Channel unavailable - check if trunk issue or bad number
exten => CHANUNAVAIL,1,NoOp(Channel unavailable - HANGUPCAUSE=${HANGUPCAUSE})
 ; Cause 1 = unallocated number, not a trunk issue
 same => n,GotoIf($["${HANGUPCAUSE}" = "1"]?bad_number)
 ; Cause 28 = invalid number format
 same => n,GotoIf($["${HANGUPCAUSE}" = "28"]?bad_number)
 ; Cause 34 = no circuit available (actual congestion)
 same => n,GotoIf($["${HANGUPCAUSE}" = "34"]?congestion)
 ; Anything else - assume trunk issue, alert ops
 same => n,Playback(all-circuits-busy-now)
 same => n,Playback(pls-try-call-later)
 same => n,Set(SAFE_NUM=${FILTER(0-9+,${CALLERID(num)})})
 same => n,Set(SLACK_MSG={"text":"*Outbound call failed* (cause=${HANGUPCAUSE})\nCaller: ${SAFE_NUM}\nEndpoint: ${CHANNEL(endpoint)}"})
 same => n,System(curl -s -X POST -H 'Content-type: application/json' --data '${SLACK_MSG}' ${SLACK_WEBHOOK} &)
 same => n,Return()
 same => n(bad_number),Playback(ss-noservice)
 same => n,Return()
 same => n(congestion),Playback(all-circuits-busy-now)
 same => n,Playback(pls-try-call-later)
 same => n,Return()

; Invalid arguments
exten => INVALIDARGS,1,NoOp(Invalid dial arguments)
 same => n,Playback(ss-noservice)
 same => n,Return()

; Catch-all for any other status
exten => _X.,1,NoOp(Unknown dial status: ${EXTEN})
 same => n,Return()

Usage in Outbound Context

[outbound]
exten => _NXXNXXXXXX,1,NoOp(Outbound to ${EXTEN})
 same => n,Gosub(set-outbound-cid,s,1)
 same => n,Dial(PJSIP/1${EXTEN}@${TRUNK},120,tT)
 same => n,Gosub(outbound-status,${DIALSTATUS},1)
 same => n,Hangup()

What the User Hears

Scenario Old Behavior New Behavior
Bad number (cause 1) "All circuits are busy now" "ss-noservice" tone
Invalid format (cause 28) "All circuits are busy now" "ss-noservice" tone
Actual trunk failure "All circuits are busy now" "All circuits are busy now" + Slack alert
Network congestion (cause 34) "All circuits are busy now" "All circuits are busy now"

Security Note

The CALLERID(num) is sanitized through FILTER(0-9+,...) before being passed to System() via curl. This prevents command injection through crafted caller ID values. Always sanitize any channel variable before using it in System().

Slack Integration

The Slack alert only fires for unknown/trunk-level failures, not for bad numbers. This prevents alert fatigue from users misdialing while still catching real trunk outages. The webhook URL is set in [globals]:

[globals]
SLACK_WEBHOOK = https://hooks.slack.com/services/XXX/YYY/ZZZ

User Notes

No notes yet. Be the first to contribute a tip or example.

Contribute a note

Share a tip, gotcha, or practical example. Keep it under 2000 characters. No questions (use the Asterisk community forums for support). Wrap code in backticks.

Moderated before publishing. Email never shown.

Related Snippets