Asterisk AMI question

I’m working with a PHP script which initiates a call using asterisk AMI and the call is initiated using the following command it appears:

Action: Originate
Channel: SIP/301
WaitTime: 30
Context: from-internal
Exten: 02081231234
Priority: 1
Async: yes

So it calls extension 301 then when that extension answers, it dials the Exten number for example 02081231234. However, extension 301 requires a pin to dial to number matching that pattern. Is it possible to use comma followed by pin and # as the Exten value for example below:

Action: Originate
Channel: SIP/301
WaitTime: 30
Context: from-internal
Exten: 02081231234,0000#
Priority: 1
Async: yes

If comma and # is not supported via this method, what are my options to include the pin the the call initialization?

So I asked AI about this and it suggested the following solution. Given how inaccurate AI can be sometimes, can someone check this and tell me if it sounds ok and if it will work? specially syntaxes and commands for FreePBX 17

  1. Edit the extensions_custom.conf file to create a new custom context as follows:
    nano /etc/asterisk/extensions_custom.conf

  2. Add this custom context at the end of the file:

[clicktocall-pin]
exten => s,1,NoOp(Click-to-Call with PIN: ${number} PIN: ${pin})
exten => s,n,Dial(${DIALSTRING}/${number},,D(wwww${pin}#))
exten => s,n,Hangup()
  1. Save and exit
    Ctrl+X, then Y, then Enter in nano

  2. Reload the dialplan:
    asterisk -rx "dialplan reload"

  3. Verify the Custom Context
    asterisk -rx "dialplan show clicktocall-pin"

  4. User variable in AMI
    Variable: number=$number,pin=$pin

How It Works:
Normal Call: Uses from-internal context and Directly dials the number

PIN Call (when conditions are met):

Uses clicktocall-pin context
Passes number and pin as variables
Custom dialplan waits 2 seconds (wwww) after remote party answers
Sends PIN followed by # as DTMF tones

You’re on the right track here with the custom context. I would update things to use the native fpbx internals wherever you can. I haven’t tested, but something like this:

[clicktocall-pin]
exten => s,1,NoOp(Click-to-Call with PIN: ${number} PIN: ${pin})
exten => s,n,Dial(local/${number}@from-internal,,D(wwww${pin}))
exten => s,n,Hangup()

and the AMI commands

Action: Originate
Channel: local/301@from-internal
WaitTime: 30
Context: clicktocall-pin
Exten: s
Priority: 1
Async: yes
Variable: number=02081231234
Variable: pin=0000#

I didn’t test any of this, but that looks about right.

1 Like

Thank you. So just to clarify, the main difference being that the for the current version of FPBX to use

Channel: local/301@from-internal

AI put the variable in one line like:

Variable: number=$number,pin=$pin

Should this be on two lines like you showed like this and with a #?

Variable: number=02081231234
Variable: pin=0000#

You can test both ways, I suspect they both work. Throw a dumpchan in the custom context and you will see the channel vars set in the full log and on the console

exten => s,1,NoOp(Click-to-Call with PIN: ${number} PIN: ${pin})
exten => s,n,DumpChan
  ... etc

Also, thinking further it would be best to use this context for originating calls, it prevents the A leg from being answered by the local voicemail:

Action: Originate
Channel: local/301@originate-skipvm

Should there be a # after the pin like the AI originally suggested? so for example like this:

exten => s,n,Dial(local/${number}@from-internal,,D(wwww${pin}#))

Only if the PIN entry requires a # terminator. If so, the # is part of the PIN and otherwise not required for the dial.

Currently when dialing out it says please enter pin followed by pound key. Does that mean I need the # in exten => s,n,Dial(local/${number}@from-internal,,D(wwww${pin}#))

oh, i asked AI and it said if the # is in the custom dialplan in this case:

exten => s,n,Dial(local/${number}@from-internal,,D(wwww${pin}#))

Then it does not need to be entered in the:

Variable: pin=0000#

So should be able to just put:

Variable: pin=0000

I don’t know how to make this any clearer.

These lines

Variable: pin=0000
exten => s,n,Dial(local/${number}@from-internal,,D(wwww${pin}#))

and these lines

Variable: pin=0000#
exten => s,n,Dial(local/${number}@from-internal,,D(wwww${pin}))

are EXACTLY the same

If the PIN includes a #, then choose one of the two locations to put it. It makes more sense to me to include it in the variable definition.

1 Like

Thank you. makes sense now. So either you place the # in one place or the other.

1 Like

I’m seeing a flaw in this thread (and in its counterpart at FreePBX forums) when discussing Originate or AMI with Local channels. No one is providing details on the fact Local channels are optimized out of the call/bridge path unless other wise told not to. That optimized channels behave in a different manner and may not have the desired results.

All three of these are different.

Channel: PJSIP/301 # Acts like an inbound call to the system. If 301 is unavailable an error will occur and AMI won't see it because the AMI action actually completed.
Channel: Local/301@from-internal # this is the default local channel method which means the Local channel will be optimized out of the call path. This has implications to the call handling.
Channel: Local/301@from-internal/n # this is a non-optimized Local channel, it means it stays present during the life of the call. Provides more control and is the required method when certain features or monitoring want to be used on the call. This includes post call handling.

So you always need to decide which method is the best. Now in the OPs case, the question to ask is “Do I need the Local channel optimized or non-optimized?” and here’s how you can break that down.

Optimized Option

Action: Originate
Channel: Local/301@originate-skipvm 
WaitTime: 30
Context: click-to-call  
Exten: s
Priority: 1
Variable: number=02081231234
Variable: pin=0000#
Async: yes

This will call 301, avoiding going to voicemail if not answered. It will check for things such as Call Waiting, Do No Disturb, Follow-Me, etc that any incoming call to 301 would be checked for. This also including dialing all the contacts if this is a PJSIP extension. Once 301 answers, it will jump to click-to-call,s,1 which will trigger the outbound call.

Click to call context

[click-to-call]
exten = s,1,NoOp(Click to Call for ${number} using PIN ${pin}) ; Will show the proper number and pin.
exten = s,n,Dial(Local/${number}@from-internal,,D(wwww${pin})) ; Should dial the number and pass the pin.
exten = s,n,Hangup

Problem: The Local channel is optimized and when it hits the Dial() it will move the Bridge to new channel (in this case another local channel) and all the variables set on the this channel will be lost. There is a very big chance that both ${number} and ${pin} will be non-existent (thus empty) when the Dial() is processed.

If things like call recording or other variables were setup on Local/301 before hitting the click-to-call context, they will be lost too. So if 301 is supposed to override the Outbound Route and always record outbound calls…that setting is ignored at this point because it’s lost.

In summary an Optimized Local channel will remove itself (and all it’s channel variables) from the call path/bridge the moment it creates a new channel. While it gives you more control over the calling channel in case it’s busy or should make a call based on X logic you lose that control once the new channel is made. Controls on features like MixMonitor, Park and others will be lost. There’s also no post-call control i.e. when the Dial() completes.

Non-Optimized Local Channel
Requires one minor modification and the local channel (and all its variables and settings stay in the call/bridge). You add /n to the end.

Channel: Local/301@originate-skipvm/n

Now this 100% guarantees that ${phonenumber} and ${pin} are accessible and do not get destroyed. It also means other settings and features triggered on the calling channel are honored and used such as Call Recording, etc. It also guarantees post-call control when the Dial() completes.

But what about the Channel: PJSIP/301 option? This is the most basic option and provides zero control over the calling channel nor any real post-call handling. This is truly a “fire and forget” method.

1 Like

Thanks for the great writeup. So just to clarify that you are making refinement suggestions that are more aligned with FreePBX best practices. The key takeaway is just adding the /n to prevent variable loss in PIN calls ? I dont have voicemail enabled for extensions so should not need to worry about calls going to voicemail.

Thanks for the advice about using /n with Local channels. I’ve implemented it but now have a new issue.

My custom dialplan context:

[clicktocall-pin]
exten => s,1,NoOp(Click-to-Call with PIN: ${number} PIN: ${pin})
exten => s,n,Dial(local/${number}@from-internal,,D(wwww${pin}#))
exten => s,n,Hangup()

AMI Originate command format:

Action: Originate
Channel: local/301@from-internal/n
WaitTime: 30
CallerId: Calling <02081231234>
Context: clicktocall-pin
Exten: s
Priority: 1
Async: yes
Variable: number=02081231234
Variable: pin=1234

The problem:

Without /n: The system would prompt “Please enter your password followed by pound key” (PIN variable was being lost)

With /n: Now it says “Incorrect password” instead of prompting (so the PIN is being passed, but apparently incorrectly)

I’m passing the correct PIN (tested manually, when it says invalid password I then enter it manually and it works), so the /n is definitely preserving the variable, but something seems wrong with how the DTMF is being sent via:

 D(wwww${pin}#).

Any ideas what could be causing the PIN to be sent incorrectly? Should I adjust the timing, DTMF method, or format?

Not sure how to debug this. You can enable DTMF logging in Settings, Asterisk Logfiles for full log and console, that might give some clues if you watch what’s happening on a live call. You can try adding a single w between the digits in case Asterisk is playing the tones too quickly. The 4 w chars preceding the PIN indicate to wait for 2 seconds (I think it’s .5 sec per w), so be sure you’re waiting long enough before playing tones.

1 Like

Just to confirm, it is .5 seconds.

1 Like

I think I’ve finally figured out what’s going wrong.

Initially, I was told that DTMF signals were being sent, and the system was rejecting the PIN because it was supposedly “incorrect” - implying it was at least receiving something. However, that information was based on someone else testing the call for me. Today, I went onsite and tested it myself, and I can confirm: no DTMF tones are being sent at all.

Here’s what I’ve done so far:

1. Verified Custom Dialplan is Being Used

The call is 100% reaching my custom dialplan (clicktocall-pin). I confirmed this via the CDR logs, and by temporarily replacing the dialplan with this test code:

[clicktocall-pin]
exten => s,1,NoOp(Click-to-Call with PIN: ${number} PIN: ${pin})
exten => s,2,SayDigits(${pin})
exten => s,3,Wait(2)
exten => s,4,Dial(local/${number}@from-internal,,D(wwwwwwww${pin}#))
exten => s,5,Hangup()

When I tested it, the system correctly read out my PIN digits, meaning the ${pin} variable is being passed and the dialplan is executing as expected.

2. Tried Sending DTMF Manually

Next, I replaced the dialplan with a simpler version to explicitly test DTMF tones:

[clicktocall-pin]
exten => s,1,Answer()
exten => s,2,SendDTMF(5)
exten => s,3,Wait(2)
exten => s,4,Hangup()

Still, I didn’t hear any DTMF tones - nothing at all.

3. Checked All DTMF Configuration Settings that i could see

  • Global SIP Settings:
    Settings → Advanced → SIP DTMF Signaling is set to RFC 2833. I tried all other options here, but no change.
  • SIP Trunk (PJSIP) Settings:
    In the PJSIP Advanced settings, DTMF is set to AUTO, with available options for RFC 4733, INFO, and INBAND. Tried changing these - still no success.
  • Extension Settings:
    The extension making the outbound call has DTMF set to RFC 2833. I changed it to AUTO, but again, no improvement.

Summary

Everything looks like it should work - the call flow is correct, variables are passed, dialplan logic is executing - but DTMF tones just aren’t being transmitted.

I’ve hit a dead end here. I feel like I’m really close, but something fundamental is missing or misconfigured.

Any help would be massively appreciated!

Thanks

you would need to have “inband” to hear them, need to debug for rfc

OK, did some digging on this. You can’t do it this way. It just won’t work. You’ll need to set up a special route for this with a prefix or something or just always enter the pin manually. Here’s why: The D() will only pass DTMF once the called party has answered. The called party hasn’t answered at this point.

Remember, this is like you picked up your phone and enter the digits yourself, you’ll still need to enter the authenticate DTMF. But the D() is meant to send DTMF to the far channel. So if you were calling your mobile phone as a test of the click-to-dial…the pin would only be sent after you answer your mobile phone and sent to your mobile phone. The authentication is happening on the calling channel (301) so it happens before the destination is actually called.

Now if you’re just trying to make sure only those that know this pin can make the call you could do something more like:

Dial(Local/${number}@from-internal,,B(clicktodial-auth^s^1(${pin}))

And make a simple gosub context that checks against the hardcoded pin (or how you want) and continues on or hangs up. The B() would run on the calling channel before attempting to dial the new channel.

So this isn’t a DTMF mode issue, it’s just the fact the DTMF isn’t being passed because the call hasn’t happened at this point.

1 Like

You think something like this will work then? Basically the users have access to a web app where they can save the PIN that is assigned to them. When they make a call through the phone to an outbound route that asks for a pin followed by # I am trying to get that part automatically entered so the user does not need to enter it each time.

[clicktocall-pin]
exten => s,1,NoOp(Click-to-Call with PIN: ${number} PIN: ${pin})
exten => s,n,Dial(Local/${number}@from-internal,,B(clicktocall-auth^s^1(${pin})))
exten => s,n,Hangup()

[clicktocall-auth]
exten => s,1,NoOp(Running click-to-call PIN injection with PIN: ${ARG1})
exten => s,n,SendDTMF(${ARG1}#)
exten => s,n,Return()

Full Timeline of Call Flow

  1. PHP AMI script causes a call to extension 301.
  2. Extension 301 picks up — dialplan in clicktocall-pin starts.
  3. Dial() is triggered, which:
  • Runs clicktocall-auth on the caller leg via B():
    • Sends the DTMF PIN (SendDTMF)
    • Returns to Dial()
  • Then proceeds to dial the external number.
  1. Call is established (301 hears ringing or connects).
  2. When the call ends, Dial() completes.
  3. Asterisk moves to the next line: Hangup(), and cleans up the call.