Astertisk 22.5.0 how send DTMF before the channel is gone

I am using asterisk 22.5.0 - I need to send DTMF tones to the OTHER end before the channels on hangup.

I tried using the Set(CHANNEL(hangup_handler_push)=send_dtmf_sub,s,1) example I found - but it does not WORK to sendDTMF as the channel is already DEAD it seems. I need to be able to send the DTMF before the OTHER side has been sent the hangup command.

HOW do I do that.

– PJSIP/117-0000002a Internal Gosub(send_dtmf_sub,s,1) start
– Executing [s@send_dtmf_sub:1] NoOp(“PJSIP/117-0000002a”, “Sending DTMF before cleanup”) in new stack
– Executing [s@send_dtmf_sub:2] SendDTMF(“PJSIP/117-0000002a”, “1”) in new stack
== Spawn extension (send_dtmf_sub, s, 2) exited non-zero on ‘PJSIP/117-0000002a’
[Mar 23 09:08:03] WARNING[4132995][C-00000ff4]: app_stack.c:1102 gosub_run: PJSIP/117-0000002a Abnormal ‘Gosub(send_dtmf_sub,s,1)’ exit. Popping routine return locations.

[send_dtmf_sub]

exten => s,1,NoOp(Sending DTMF before cleanup)

exten => n,SendDTMF(11,a)

exten => n,Return()

Help

Jerry

The hangup handler runs after the channel is already being torn down, so SendDTMF() won’t work there — the media path is gone by the time your subroutine executes.

What you want is the h extension or, better, a pre-disconnect hook. A few options depending on what triggers the hangup:

Option 1: Use the h extension (works if YOUR side hangs up)

exten => h,1,Set(CHANNEL(hangup_handler_wipe)=)
 same => n,SendDTMF(1)
 same => n,Wait(1)
 same => n,Return()

Problem: by the time h fires, the bridge is already torn down on most call flows. This has the same timing issue you’re hitting.

Option 2: Use CONNECTEDLINE() or in-dialog re-INVITE with DTMF before you initiate the hangup

If your dialplan is the one triggering the hangup (not the remote end), send the DTMF before you call Hangup():

exten => hangup_with_dtmf,1,SendDTMF(1,250,300)
 same => n,Wait(1)
 same => n,Hangup()

Option 3: Use AUDIOHOOK_INHERIT + B() option on Dial

If you need the DTMF sent to the called party’s channel specifically, use the B() option on Dial() to run a subroutine on the callee’s channel before they’re bridged — but that’s pre-answer, not pre-hangup.

Option 4 (most reliable): Catch the hangup signal and race the DTMF

Use an AMI event listener or ARI to catch the hangup event and inject DTMF via POST /channels/{channelId}/dtmf before the channel fully tears down. ARI gives you a small window (~500ms) between the hangup signal and actual channel destruction.

POST /ari/channels/{channelId}/dtmf?dtmf=1&duration=250

The real question is — what’s the remote end expecting? If it’s a billing system or call recording trigger that needs to see DTMF before disconnect, you might need to reverse the hangup flow so your side sends DTMF first, waits for acknowledgment, then initiates BYE.

I went through the full Asterisk dialplan flow for call handling and disconnect sequences in a writeup on Asterisk configuration for call centers — the hangup handler timing is one of the things that trips people up when the predictive dialer needs to signal disposition codes to upstream carriers.

hi gamlin - SEEMS like only option 4 has a chance of working. I have no control of the hangup. Its basically a polycome phone hanging up so not me - and the channel is DEAD already for options 1-3 above. The “other” side needs two DTMF to say its DONE or it never releases the line. DTMF 1 for ACKnowledge and then another DTMF 1 to end the call. if it does not receive those it never GIVES up the line. So what is the quickest way to do AMI - catch hangup - and send DTMF 11 - thanks

Jerry

Depending on which direction you are referring to use either the F or g argument to Dial to keep the respective channel alive.

[1] Dial - Asterisk Documentation

Thanks jcolp - I had tried both of those - but its seems the “other” channel is DEAD AND gone - the pbx.cli - does show with F and g asterisk still executing - but I could not successfully send DTMF to the other end - channel was gone.

Jerry

The options work. If they don’t, then you need to actually show the console output so we can see what is actually going on.

here is Dialplan

exten => 911,1,Dial(PJSIP/VC-jerryv-geisv,5,F(smvoice-pjsip-aor-hangup,s,1))

[smvoice-pjsip-aor-hangup]

exten => s,1,NoOp(Sending DTMF before hangup)

exten => s,n,SendDTMF(1)

exten => s,n,Wait(1)

exten => s,n,SendDTMF(1)

Here is CLI

-- Executing \[911@smvoice-pjsip:3\] Dial("PJSIP/117-0000004d", "PJSIP/VC-jerryv-geisv,5,F(smvoice-pjsip-aor-hangup,s,1)") in new stack

-- Called PJSIP/VC-jerryv-geisv

-- PJSIP/VC-jerryv-geisv-0000004e is ringing

-- PJSIP/VC-jerryv-geisv-0000004e answered PJSIP/117-0000004d

-- Channel PJSIP/VC-jerryv-geisv-0000004e joined 'simple_bridge' basic-bridge <4e0eb467-7a6e-4f63-9248-d4c961fe6791>

-- Channel PJSIP/117-0000004d joined 'simple_bridge' basic-bridge <4e0eb467-7a6e-4f63-9248-d4c961fe6791>

-- Channel PJSIP/VC-jerryv-geisv-0000004e left 'simple_bridge' basic-bridge <4e0eb467-7a6e-4f63-9248-d4c961fe6791>

-- Channel PJSIP/117-0000004d left 'simple_bridge' basic-bridge <4e0eb467-7a6e-4f63-9248-d4c961fe6791>

== Spawn extension (smvoice-pjsip, 911, 3) exited non-zero on ‘PJSIP/117-0000004d’

-- Executing \[h@smvoice-pjsip:1\] NoOp("PJSIP/117-0000004d", "agi_pa_meetme= agi_use_meetme agi_use_confbridge=") in new stack

-- Executing \[h@smvoice-pjsip:2\] AGI("PJSIP/117-0000004d", "smvoice,-digium_success,-pa_done,,") in new stack

-- Launched AGI Script /var/lib/asterisk/agi-bin/smvoice

-- <PJSIP/117-0000004d>AGI Script smvoice completed, returning 0

-- Executing \[h@smvoice-pjsip:3\] Hangup("PJSIP/117-0000004d", "") in new stack

== Spawn extension (smvoice-pjsip, h, 3) exited non-zero on ‘PJSIP/117-0000004d’

It never runs the F

Jerry

Who hung up, and who are you expecting to execute the dialplan?

The F option is for the called party, so if PJSIP/VC-jerryv-geisv hung up then it would do nothing. If you want the calling party to continue you’d use “g”.

I have a call Oringinating from BOXA - calliing into asterisk dialplan 911 and then it Dials VC-jerryv-geisv. Its the VC-jerryv-geisv that gets hungup. I am trying to send commands to the BOXA

jerry

If you want the calling party to continue, you’d use the g option then.

Thanks WIll try tomorrow

jerry

Ok - I could not get anythign working with gF - so I thought I would try the ARI

Here are my TWO channels

Channel Location State Application(Data)
PJSIP/123-00000003 911@smvoice-pjsip:3 Up Dial(PJSIP/VC-jerryv-geisv,5)
PJSIP/VC-jerryv-geisv-00000005 (None) Up AppDial((Outgoing Line))

my ARI command with the correct CHANNELID:

POST /ari/channels/PJSIP/123-00000003/dtmf?dtmf=1&duration=250

* Trying 192.168.1.8:8088…
* Connected to 192.168.1.8 (192.168.1.8) port 8088
* Server auth using Basic with user ‘lsiari’

POST /ari/channels/PJSIP/123-00000003/dtmf?dtmf=1&duration=250 HTTP/1.1
Host: 192.168.1.8:8088
Authorization: Basic bHNpYXJpOmxzaWFyaXB3
User-Agent: curl/8.5.0
Accept: */*

< HTTP/1.1 404 Not Found
< Server: LSIAsterisk
< Date: Tue, 24 Mar 2026 13:43:56 GMT
< Cache-Control: no-cache, no-store
< Content-type: application/json
< Content-Length: 32
<
* Connection #0 to host 192.168.1.8 left intact
{“message”:“Resource not found”}

I get error - what did I do wrong ?

Thanks

Jerry

Before jumping to ARI you really should have provided logging showing how “g” didn’t work.

As for ARI, it doesn’t accept a channel name in that. It wants the channel uniqueid.

Seems ODD that channel information is not what is needed there or would work… but…

so I LOOKED up the unique ID - tried again - new error about statis ???

> POST /ari/channels/1774359780.46/dtmf?dtmf=1&duration=250 HTTP/1.1

> Host: 192.168.1.8:8088

> Authorization: Basic bHNpYXJpOmxzaWFyaXB3

> User-Agent: curl/8.5.0

> Accept: */*

>

< HTTP/1.1 409 Conflict

< Server: LSIAsterisk

< Date: Tue, 24 Mar 2026 13:57:11 GMT

< Cache-Control: no-cache, no-store

< Content-type: application/json

< Content-Length: 47

<

* Connection #0 to host 192.168.1.8 left intact

{“message”:“Channel not in Stasis application”}

I just want to send a DTMF tone over an active channel .

What do I do with that?

Jerry

You can’t use ARI for that. Almost all ARI operations require the channel to be in ARI. If it’s not, you can’t touch it.

So I am back to my original question.

I “need” to be able to send DTMF 1 and 1 to tell the other end to ACK and hangup before the channel is dead - how do I do that? This would be on the ORIGINATING line - not the line that answers.

Below F does not get ran .

; PHONE TREE - AOR 911 testing

exten => 911,1,Gosub(smvoice-toa-check-callerid,s,1)

exten => 911,n,agi(smvoice,-digium_asterisk,-LogPhone,VC-jerryv-geisv,-LogIncomingCall)

exten => 911,n,Dial(PJSIP/VC-jerryv-geisv,5,gF(smvoice-pjsip-aor-hangup,aor-hangup,1))

exten => 911,n,Noop(loop start again at priority 2)

exten => 911,n,Wait(1)

exten => 911,n,Goto(2)

This is the basic dialplan (at this time) its looping (typically calling more than one person till answer) but I only need ME at this time… But the originating device needs DTMF 11 to ack and hangup.

Connected to Asterisk 22.5.0 currently running on devgeis (pid = 2254744)

-- Executing \[911@smvoice-pjsip:1\] Gosub("PJSIP/123-00000008", "smvoice-toa-check-callerid,s,1") in new stack

-- Executing \[s@smvoice-toa-check-callerid:1\] System("PJSIP/123-00000008", "/usr/bin/test -e /home/silentm/toa_callerid_number.txt") in new stack

-- Executing \[s@smvoice-toa-check-callerid:2\] GotoIf("PJSIP/123-00000008", "1?toa_skip_callerid_number:toa_use_callerid_number") in new stack

-- Goto (smvoice-toa-check-callerid,s,5)

-- Executing \[s@smvoice-toa-check-callerid:5\] NoOp("PJSIP/123-00000008", "1") in new stack

-- Executing \[s@smvoice-toa-check-callerid:6\] System("PJSIP/123-00000008", "/usr/bin/test -e /home/silentm/toa.incoming.txt") in new stack

-- Executing \[s@smvoice-toa-check-callerid:7\] GotoIf("PJSIP/123-00000008", "1?toa_skip_callerid:toa_use_callerid") in new stack

-- Goto (smvoice-toa-check-callerid,s,15)

-- Executing \[s@smvoice-toa-check-callerid:15\] NoOp("PJSIP/123-00000008", "No TOA Incoming call") in new stack

-- Executing \[s@smvoice-toa-check-callerid:16\] Return("PJSIP/123-00000008", "") in new stack

-- Executing \[911@smvoice-pjsip:2\] AGI("PJSIP/123-00000008", "smvoice,-digium_asterisk,-LogPhone,VC-jerryv-geisv,-LogIncomingCall") in new stack

-- Launched AGI Script /var/lib/asterisk/agi-bin/smvoice

-- <PJSIP/123-00000008>AGI Script smvoice completed, returning 0

-- Executing \[911@smvoice-pjsip:3\] Dial("PJSIP/123-00000008", "PJSIP/VC-jerryv-geisv,5,gF(smvoice-pjsip-aor-hangup,aor-hangup,1)") in new stack

-- Called PJSIP/VC-jerryv-geisv

-- PJSIP/VC-jerryv-geisv-00000009 is ringing

-- Nobody picked up in 5000 ms

-- Executing \[911@smvoice-pjsip:4\] NoOp("PJSIP/123-00000008", "loop start again at priority 2") in new stack

-- Executing \[911@smvoice-pjsip:5\] Wait("PJSIP/123-00000008", "1") in new stack

-- Executing \[911@smvoice-pjsip:6\] Goto("PJSIP/123-00000008", "2") in new stack

-- Goto (smvoice-pjsip,911,2)

-- Executing \[911@smvoice-pjsip:2\] AGI("PJSIP/123-00000008", "smvoice,-digium_asterisk,-LogPhone,VC-jerryv-geisv,-LogIncomingCall") in new stack

-- Launched AGI Script /var/lib/asterisk/agi-bin/smvoice

-- <PJSIP/123-00000008>AGI Script smvoice completed, returning 0

-- Executing \[911@smvoice-pjsip:3\] Dial("PJSIP/123-00000008", "PJSIP/VC-jerryv-geisv,5,gF(smvoice-pjsip-aor-hangup,aor-hangup,1)") in new stack

-- Called PJSIP/VC-jerryv-geisv

-- PJSIP/VC-jerryv-geisv-0000000a is ringing

-- PJSIP/VC-jerryv-geisv-0000000a answered PJSIP/123-00000008

-- Channel PJSIP/VC-jerryv-geisv-0000000a joined 'simple_bridge' basic-bridge <edae119f-adf0-468b-9fd0-04d3bac48cdd>

-- Channel PJSIP/123-00000008 joined 'simple_bridge' basic-bridge <edae119f-adf0-468b-9fd0-04d3bac48cdd>

== Manager ‘LayeredSolutions’ logged on from 127.0.0.1

-- Called s@smvoice-heartbeat/n

-- Executing \[s@smvoice-heartbeat:1\] UserEvent("Local/s@smvoice-heartbeat-0000002f;2", "HeartBeat, Noop") in new stack

-- Executing \[s@smvoice-heartbeat:2\] Hangup("Local/s@smvoice-heartbeat-0000002f;2", "") in new stack

== Spawn extension (smvoice-heartbeat, s, 2) exited non-zero on ‘Local/s@smvoice-heartbeat-0000002f;2’

== Manager ‘LayeredSolutions’ logged off from 127.0.0.1

== Manager ‘LayeredSolutions’ logged on from 127.0.0.1

== Manager ‘LayeredSolutions’ logged off from 127.0.0.1

-- Channel PJSIP/VC-jerryv-geisv-0000000a left 'simple_bridge' basic-bridge <edae119f-adf0-468b-9fd0-04d3bac48cdd>

-- Channel PJSIP/123-00000008 left 'simple_bridge' basic-bridge <edae119f-adf0-468b-9fd0-04d3bac48cdd>

-- Executing \[911@smvoice-pjsip:4\] NoOp("PJSIP/123-00000008", "loop start again at priority 2") in new stack

-- Executing \[911@smvoice-pjsip:5\] Wait("PJSIP/123-00000008", "1") in new stack

-- Executing \[911@smvoice-pjsip:6\] Goto("PJSIP/123-00000008", "2") in new stack

-- Goto (smvoice-pjsip,911,2)

-- Executing \[911@smvoice-pjsip:2\] AGI("PJSIP/123-00000008", "smvoice,-digium_asterisk,-LogPhone,VC-jerryv-geisv,-LogIncomingCall") in new stack

-- Launched AGI Script /var/lib/asterisk/agi-bin/smvoice

-- <PJSIP/123-00000008>AGI Script smvoice completed, returning 0

-- Executing \[911@smvoice-pjsip:3\] Dial("PJSIP/123-00000008", "PJSIP/VC-jerryv-geisv,5,gF(smvoice-pjsip-aor-hangup,aor-hangup,1)") in new stack

-- Called PJSIP/VC-jerryv-geisv

-- PJSIP/VC-jerryv-geisv-0000000b is ringing

== Spawn extension (smvoice-pjsip, 911, 3) exited non-zero on ‘PJSIP/123-00000008’

-- Executing \[h@smvoice-pjsip:1\] NoOp("PJSIP/123-00000008", "agi_pa_meetme= agi_use_meetme agi_use_confbridge=") in new stack

-- Executing \[h@smvoice-pjsip:2\] AGI("PJSIP/123-00000008", "smvoice,-digium_success,-pa_done,,") in new stack

-- Launched AGI Script /var/lib/asterisk/agi-bin/smvoice

-- <PJSIP/123-00000008>AGI Script smvoice completed, returning 0

-- Executing \[h@smvoice-pjsip:3\] Hangup("PJSIP/123-00000008", "") in new stack

== Spawn extension (smvoice-pjsip, h, 3) exited non-zero on ‘PJSIP/123-00000008’

dialplan show smvoice-pjsip-aor-hangup

[ Context ‘smvoice-pjsip-aor-hangup’ created by ‘pbx_config’ ]

‘aor-hangup’ => 1. NoOp(Sending DTMF before cleanup for AOR) [extensions.conf:791]

                2\. SendDTMF(11,a)                             \[extensions.conf:792\]

                3\. Wait(1)                                    \[extensions.conf:793\]

                4\. SendDTMF(11)                               \[extensions.conf:794\]

                5\. Hangup()                                   \[extensions.conf:795\]

Jerry

As I’ve stated before, it won’t. “F” works on called party of Dial(). If the called party hangs up, then F will do nothing.

The “g” option IS working. You can see it in the console output. After PJSIP/VC-jerryv-geisv hangs up the dialplan continues. So now you need to alter the dialplan after it to do what you want. If you only want to hang up the calling party if the called party has answered you would examine the DIALSTATUS dialplan variable and check it for ANSWERED.

jcolp - Thanks!

It seems the using the g to continue and the checking the DIALSTATUS - is working for me.

jerry