Hooking for fun and income

Despite the frequency with which it arises here in the forum, there is not yet a good resource for learning to use dialplan hooks in FreePBX. This will probably end up in the wiki at some point, but until that happens, here are the broad strokes for leveraging dialplan hooks in FreePBX 14.

What is a dialplan hook?

A dialplan hook refers to several pre-defined FreePBX contexts that exist solely for users to add their own Asterisk Macros to be run at specific locations in the call flow. They all occur immediately before a Dial() application and are used to run custom dialplan to perform some function not supported by the GUI. As a user, you define dialplan hooks as macros in the file /etc/asterisk/extensions_custom.conf as individual asterisk contexts. Use whatever method you are comfortable with for editing the file; beginners may find is useful to use the Config Edit module. You may be aware of something in Asterisk called a “Predial Handler”. Despite the name similarity with the macros that follow, they are completely separate things having nothing to do with each other.

Aren’t macros deprecated in Asterisk?

Yes. However, as of this writing (with 15 in beta) , FreePBX core still relies on dozens of Macros. This will have to be dealt with in time, and I have already opened a ticket for dealing with the predial hook macros. At some point this post will need updates to show how to use the predial subroutines.

How do I add custom SIP headers?

This entire post is a lead in to what is really needed, and that is a guide to adding custom SIP headers in a supportable way. This is beyond the scope of today’s sermon but will form the basis of a future thread. I have been working with SIP headers a lot lately, and have been reminded once again of all the flawed internet how to’s or recommended methods that may once have worked, but no longer. For the longest time we had one SIP driver, and got comfortable with methods for adding SIP headers to chan_sip channels. When PJSIP came along, those methods didn’t work for pjsip channels, so new methods were devised. FreePBX 14 introduced Dial changes that now break some of the early methods for adding headers for PJSIP channels. The end result is that virtually all of the posted methods for adding custom SIP headers are incomplete at best, or at worst are completely useless for FreePBX 14.

When do I use a dialplan hook and when do I use a Custom Destination?

If you’re able to structure your call flow such that the custom dialplan you want to execute looks something like this:

Inbound Route -> Time Condition -> <custom dialplan> -> Ring Group

Then you don’t need (or want) to use a dialplan hook. The above is better done with a Custom Destination. Custom Destinations are preferred over dialplan hooks so use the GUI wherever possible.

How do I do X on every outbound call?

The first (and for the longest time, the only) dialplan hook going back more than a decade is macro-dialout-trunk-predial-hook. There are many examples floating around the 'net on how to use it, and it’s existence is testament to all the configuration edge cases for the various SIP providers around the world. If your provider requires some specific setting that can’t be done via the GUI, you would use this hook in order to make the necessary channel modifications prior to the trunk dial. A bare bones example would look like this:

exten => s,1,NoOp(Entering user defined context macro-dialout-trunk-predial-hook in extensions_custom.conf)
; additional lines here 
exten => s,n,MacroExit

The above example is trivial, it only records a single line in the Asterisk full log and then exits sending the call back to the FreePBX generated dialplan. An extension of s is used as is required for all Macros. You can see the above in action by saving the change, reloading the dialplan and make a call after running the following at the bash prompt:

[[email protected] ~]# tail -f /var/log/asterisk/full | grep predial

[2019-04-06 10:46:09] VERBOSE[20229][C-00000011] pbx.c: Executing [[email protected]:20] Macro("PJSIP/5004-00000024", "dialout-trunk-predial-hook,") in new stack
[2019-04-06 10:46:09] VERBOSE[20229][C-00000011] pbx.c: Executing [[email protected]:1] NoOp("PJSIP/5004-00000024", "Entering user defined context macro-dialout-trunk-predial-hook in extensions_custom.conf") in new stack
[2019-04-06 10:46:09] VERBOSE[20229][C-00000011] pbx.c: Executing [[email protected]:3] MacroExit("PJSIP/5004-00000024", "") in new stack

Once the bare bones dialplan is in place and confirmed working with tail, the hard part begins where you actually write useful dialplan that both works for all edge cases and doesn’t break all the other edge cases.

How do I do X on every local call?

It’s only a little less straight forward to perform similar action on local extension calls. There are several dialplan hooks used for calls to local extensions depending on the ring strategy used to reach the extension. You can see the dialplan hooks in use on local calls by running the same tail from above. So focusing on a simple case where local extension 5004 calls local extension 5005 with no FMFM enabled, you would see the following:

[[email protected] ~]# tail -f /var/log/asterisk/full | grep predial

[2019-04-06 10:50:07] VERBOSE[20945][C-00000012] pbx.c: Executing [[email protected]:52] Macro("PJSIP/5004-00000026", "dialout-one-predial-hook,") in new stack
[2019-04-06 10:50:07] VERBOSE[20945][C-00000012] pbx.c: Executing [[email protected]:1] MacroExit("PJSIP/5004-00000026", "") in new stack

The above shows us that just before a call goes to a local extension, the Macro macro-dialout-one-predial-hook is called. Using the same technique as above, we can add our own dialplan such as:

exten => s,1,Noop(Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf)
; add additional lines here
exten => s,n,MacroExit

After save and reload, the tail confirms it’s working:

[[email protected] ~]# tail -f /var/log/asterisk/full | grep predial

[2019-04-06 10:57:00] VERBOSE[22171][C-00000013] pbx.c: Executing [[email protected]:52] Macro("PJSIP/5004-00000027", "dialout-one-predial-hook,") in new stack
[2019-04-06 10:57:00] VERBOSE[22171][C-00000013] pbx.c: Executing [[email protected]:1] NoOp("PJSIP/5004-00000027", "Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf") in new stack
[2019-04-06 10:57:00] VERBOSE[22171][C-00000013] pbx.c: Executing [[email protected]:2] MacroExit("PJSIP/5004-00000027", "") in new stack

How do I apply custom dialplan selectively on some outbound calls?

There is no single simple answer to this and sometimes the answer is ‘you can’t’. But usually one can identify a channel variable that is set to a unique value for all the calls you want to modify, and if so, that allows you to write dialplan that only acts on those calls. A simple example would be dialplan that does something different if the local call originates from extension 5004:

exten => s,1,Noop(Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf)
exten => s,n,GotoIf($["${AMPUSER}"="5004"]?special)
exten => s,n,MacroExit
exten => s,n(special),NoOp(Incoming call from etension 5004)
exten => s,n,MacroExit

Call from 5004:

[[email protected] ~]# tail -f /var/log/asterisk/full | grep predial

[2019-04-06 11:55:35] VERBOSE[31283][C-00000016] pbx.c: Executing [[email protected]:52] Macro("PJSIP/5004-0000002a", "dialout-one-predial-hook,") in new stack
[2019-04-06 11:55:35] VERBOSE[31283][C-00000016] pbx.c: Executing [[email protected]:1] NoOp("PJSIP/5004-0000002a", "Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf") in new stack
[2019-04-06 11:55:35] VERBOSE[31283][C-00000016] pbx.c: Executing [[email protected]:2] GotoIf("PJSIP/5004-0000002a", "1?special") in new stack
[2019-04-06 11:55:35] VERBOSE[31283][C-00000016] pbx_builtins.c: Goto (macro-dialout-one-predial-hook,s,4)
[2019-04-06 11:55:35] VERBOSE[31283][C-00000016] pbx.c: Executing [[email protected]:4] NoOp("PJSIP/5004-0000002a", "Incoming call from etension 5004") in new stack
[2019-04-06 11:55:35] VERBOSE[31283][C-00000016] pbx.c: Executing [[email protected]:5] MacroExit("PJSIP/5004-0000002a", "") in new stack

Call from 5016:

[[email protected] ~]# tail -f /var/log/asterisk/full | grep predial

[2019-04-06 11:55:45] VERBOSE[31493][C-00000017] pbx.c: Executing [[email protected]:52] Macro("PJSIP/5016-0000002b", "dialout-one-predial-hook,") in new stack
[2019-04-06 11:55:45] VERBOSE[31493][C-00000017] pbx.c: Executing [[email protected]:1] NoOp("PJSIP/5016-0000002b", "Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf") in new stack
[2019-04-06 11:55:45] VERBOSE[31493][C-00000017] pbx.c: Executing [[email protected]:2] GotoIf("PJSIP/5016-0000002b", "0?special") in new stack
[2019-04-06 11:55:45] VERBOSE[31493][C-00000017] pbx.c: Executing [[email protected]:3] MacroExit("PJSIP/5016-0000002b", "") in new stack

And that leads to the obvious question, how do I know which channel variable to check and for what value? Each situation is unique so you need to figure that out for yourself. In doing so the DumpChan dialplan application is essential. Otherwise start a thread asking for ideas.

How do I perform a custom action on hangup?

Nothing to it. Using the techniques above, you would add a hangup handler to the channel. A hangup handler, as the name implies, is a subroutine that runs after the channel hangs up. A simple example:

The dialplan:

exten => s,1,Noop(Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf)
exten => s,n,Set(CHANNEL(hangup_handler_push)=lgaetz-do-this-on-hangup,s,1)
exten => s,n,MacroExit

exten => s,1,Noop(Entering user defined context lgaetz-do-this-on-hangup in extensions_custom.conf)
; additional lines 
exten => s,n,Return

The log entries:

[[email protected] asterisk]# tail -f /var/log/asterisk/full | grep lgaetz

[2019-04-06 16:31:31] VERBOSE[7503][C-00000018] pbx.c: Executing [[email protected]:2] Set("SIP/5002-00000007", "CHANNEL(hangup_handler_push)=lgaetz-do-this-on-hangup,s,1") in new stack
[2019-04-06 16:31:35] VERBOSE[7503][C-00000018] app_stack.c: SIP/5002-00000007 Internal Gosub(lgaetz-do-this-on-hangup,s,1) start
[2019-04-06 16:31:35] VERBOSE[7503][C-00000018] pbx.c: Executing [[email protected]:1] NoOp("SIP/5002-00000007", "Entering user defined context lgaetz-do-this-on-hangup in extensions_custom.conf") in new stack
[2019-04-06 16:31:35] VERBOSE[7503][C-00000018] pbx.c: Executing [[email protected]:2] Return("SIP/5002-00000007", "") in new stack
[2019-04-06 16:31:35] VERBOSE[7503][C-00000018] app_stack.c: SIP/5002-00000007 Internal Gosub(lgaetz-do-this-on-hangup,s,1) complete GOSUB_RETVAL=

Continued in part 2 - Virtuous Signalling

Virtuous Signalling
[SOLVED] Use from-internal-custom or dialplan hook
How to add sip header to incoming call?
How to insert P-Early-Media: supported
Emergency call and prepend with A-D key
[from-internal-custom] screws outgoing CID
[How To] Get Caller ID Override working with DISA
Async AGI - How to do in with SNG 7
Dial Plan - Dropping extra digits
What is the purpose of all the includes
Dial hooks for offline extensions
SIP Header
How do I modify the P-asserted Identity
How to hide inbound phone number i softphone
Using *-custom contexts overwrites dialplan
Blacklist question
GotoIf Not Working in Queues
Pass-through Alert-Info header from phone
Custom Destination Hangup Cause Question
How works extensions_custom.conf
Limit call duration to / from an internal extension
RasPBX bad sound quality when using a dongle
Outbound call fails because number is not included in the sip invite
Email or SMS alert when caller hungs up
Suggestions for future versions of FreePBX
Add SipHeader to all extensions (PJSIP)
How works extensions_custom.conf
How to embed custom fields for recording file name into sip header
Queue status
Outbound route cid stopped working
Predial-hook question
FreePBX AllowList Caller Module -- to block all but a list of allowed callers
How to launch script from extensions.conf
Play message to caller depending on dial pattern in outbound route
Fwconsole reload remove the dial plan settings from extensions_additional.conf
Running a python script when call is answered
[macro-hangupcall-custom] never gets called?
How-To: Missed Calls Notifications
Limit call time on a specific trunk [SOLVED] (in my way ....)
Screen Caller, Announce Caller, and give Callee Options?
Time & Temp Announcer Script
Extensions_custom.conf - empty header - replace possible?
Asterisk EAGI run on incoming call
Caller ID Zoiper
FreePBX AllowList Caller Module -- to block all but a list of allowed callers
Freepbx DID issue in outgoing
How to run the script after the call ends?
How to send email on every call received at a DID, even if it gets answers, leaves vm, hangs up, etc?
Caller ID RingGroup
Play announcement before dial from certain extension
FreePBX followme and caller ID issues
How Blacklist menu works?
Limit calls depend on number
Rerouted inbound call to external destination does not preserve CID
FreePBX 15, unable to override…
Dial plan problem? Configuration problem? HELP!
Protecting callers data “Caller ID”
Yealink T48s call forward issue
Run an AGI Script (or something else) when agent joins/leaves a queue?
Execute custom code on beginning of call (inbound route for internal calls)
Context for scripts (incoming calls)
Survey after Hangup
How to set up so that after dialing a number, the person will hear the announcement first
Appending to the caller ID in outbound route
Open Source Pro Tips #6 - Remote Phone Using the Built-in VPN
CRM commercial Module with Insurance software
How to limit call duration in FreePBX/Asterisk for echo test
Launch survey at the end of Call answered by the agent
Extension Rerouting
Can Zulu client open a link to URL when starting an outgoing call?
Change the CALLERID
Recording caller and callee voice in separate files
Same extension but 2 different outbound CID for 2 trunks
Same extension but 2 different outbound CID for 2 trunks
BLF not working when extensions are in custom context
GUI method for storing ivr choise on db
NFON Trunk
Implementing a Boss / Secretary Call Screening
IVR and hangup
SIP Header not added
Cascade dialplan for dialing emergency engineers as quick as possible
CallerID Lookup for internal call
Latest Creation - Asterisk Overlay (Queues) on Avaya Endpoints
Follow Me failing on Anonymous number
What if I only wanted some extensions to use the predial hook context
Adding to an existing dialplan in extensions_custom.conf
Hangup hook to put back a call into a queue
Replace 10-digit CallerID from PSTN calls to 11-digit CallerID for followme
Getting the calls recording links
Queue outbound Caller ID number
Execute dialplan action after each channel hangs up
CID Superfecta: Get UniqueID from Call
Monitor an extension's state to allow a script to be run
Freepbx 13, CID via cisco trunk suggestions
Continue in Dialplan when caller hangs up in QUEUE
P-Access-Network-Info SIP Header transmission for INVITE
Restrict inbound calls to an extension - used for Door Lock Release
Custom Dialplan - Softphone doesn't record but DISA does
Custom dialplan
Callers not leaving Voicemail reports
Extensions_additional.conf | Where are the include elements located?
CDR not resolve names of internal numbers in Destination on incoming and internal calls
Include custom dial plan after trunk was selected?
Working with Asterisk ARI
Dialplan quesiton. Which file to edit?
Anyway to create a truly dynamic 'followme'?
Call limit
Outbound CID how to add +country code
Zulu no audio when dialing through DAHDI
Modifying existing Macro with module
[Solved] Keep Certain Extensions from Receiving Caller ID Completely?
[Solved] Keep Certain Extensions from Receiving Caller ID Completely?
Announcement / IVR before dial-out
Gosub error in macro-dial-one
Disabling voicemail for intra-PBX (extension to extension) calls
Disallowing Non-Queue Calls When Logged In
Limit on the length of a call from ext to ext?
External call forwarding with Sip Trunk and the p-preferred-identity
Run a php script when a call comes in
IAX2 Trunk use the wrong context
How to make dialplan for webphone, cell phone?
How can make custom dialplan?
Detecting If 911 Is Called From Any Phone
Freepbx ticket system (call center)
Blacklist outgoing calls
DISA and Mobile Contact Center
Update query inside cdr table within the call
Restrict simultaneous calls
Forward original caller id to a external mobile
PinSet password, followed by #
Two CallerID formats, One Route
Limit call duration for internal calls only, not external: problems with IVRs
PJSIP Auto Answer
Add onto from-internal-xfer with extensions_custom
Blacklist failing

Will the use and configuration of these dial plan macros be affected by the macro functionality being deprecated in Asterisk?

[post updated to address this - mod]


Good stuff here!! Thank you for the nice write up, Lorne!

1 Like

Well since Asterisk 13 (as far as I can recall) the use of Pre-Dialer hooks have been in place for applying SIP Headers with something like exten => _X.,1,GoSub(func-set-sipheaders,s,1(X-Header,header-value) and when all the Dial() commands are executed they are run with the options b(func-apply-sipheaders^s^1) which in turn runs this GoSub that will use the proper ADDHeader (SIP or PJSIP) based on the channel type.

I use these for all my custom dialplan to hook in PJSIP headers as the only place you can apply them is in the pre-dial hook with the b() dial option. So this has been in place for quite some time. Now the earlier versions of 13 didn’t have the PJSIP check (real early versions) so yeah, that broke adding PJSIP headers with the function but it’s been fixed for sometime and has been this way on v14.

include => func-apply-sipheaders-custom
exten => s,1,Noop(Applying SIP Headers to channel ${CHANNEL})
exten => s,n,Set(TECH=${CUT(CHANNEL,/,1)})
exten => s,n,While($["${SET(sipkey=${SHIFT(SIPHEADERKEYS)})}" != “”])
exten => s,n,Set(sipheader=${HASH(SIPHEADERS,${sipkey})})
exten => s,n,ExecIf($["${sipheader}" = “unset” & “${TECH}” = “SIP”]?SIPRemoveHeader(${sipkey}:))
exten => s,n,ExecIf($["${sipheader}" = “unset” & “${TECH}” = “PJSIP”]?Set(PJSIP_HEADER(remove,${sipkey})=))
exten => s,n,ExecIf($["${sipkey}" = “Alert-Info” & ${REGEX("^<[^>]*>" ${sipheader})} != 1]?Set(sipheader=;info=${sipheader}))
exten => s,n,ExecIf($["${TECH}" = “SIP”]?SIPAddHeader(${sipkey}:${sipheader}))
exten => s,n,ExecIf($["${TECH}" = “PJSIP”]?Set(PJSIP_HEADER(add,${sipkey})=${sipheader}))
exten => s,n,EndWhile
exten => s,n,Return()

I believe that there is a problem with macro-dialout-trunk-predial-hook:

Before calling the hook, the variable ‘custom’ is set to the trunk name, e.g. ‘SIP/Flowroute’, so the user’s code can take appropriate actions for specific trunks. AFAIK, this variable is not read by FreePBX – it exists solely to aid hook authors. Unfortunately, because of pjsip differences, pjsip trunks set custom to just ‘PJSIP’. If you have multiple pjsip trunks requiring differing hook actions, you need to fish the trunk name out of various internal variables, which is likely not robust against updates. I hope that macro-dialout-trunk will be updated to correct this problem.

1 Like

I don’t think this is a bug. The channel variable custom is used by FreePBX dialplan to identify trunks of type custom. The fact that it is populated with the peer name of chan_sip trunks is a byproduct not its intent.

There is a supportable way to determine the trunk name without relying on custom:

  1. Determine value of channel var DIAL_TRUNK, it will be an integer
  2. Determine value of global var OUT_${DIAL_TRUNK} this will be the trunk tech/name if SIP or IAX but will only be the tech if PJSIP.
  3. If value from 2 == “PJSIP” then the trunk name will be in global variable OUT_${DIAL_TRUNK}_SUFFIX in the format @trunkname

Distilling the above:

exten => s,n,ExecIF($["${OUT_${DIAL_TRUNK}_SUFFIX}"!=""]?Set(trunk_name=${OUT_${DIAL_TRUNK}_SUFFIX}):Set(trunk_name=${OUT_${DIAL_TRUNK}}))
exten => s,n,Noop(trunk name: ${trunk_name})

That technique will work for all endpoints in 14, but not for outbound calls in 13, so I take issues with the claim of “quite some time”. My point still stands tho, doing a google or forum search for “adding sip header” will yield plenty of results that don’t work, or only partially work.

I’m not sure how to reply to this without sounding like a complete jerk about it, so here it goes. It’s been 22 months since the release of v14, in my terms that is quite some time. Now if you’re going to take issue with my use of a phrase then my reply to that is simple.

I take issue with the fact that PJSIP, since its introduction, has documented that setting PJSIP headers requires them to be on the called channel that needs them. To do so one must use the pre-dial handler options that were introduced in v11 so that means the b() option for the Dial() string. So that would mean since FreePBX v12 until now for v13, almost five years now, not a single person involved with the FreePBX Project caught this and applied a fix for it? It makes it even worse that v14 does have the pre-dial handlers and solves it and not a single person still thought “We should do this for v13 since it is still a supported release”. This is something that never should have been in issue in v13 at all and definitely should still not be an issue this late in the game on v13.

I am also curious as to why with v15 you are still relying on Macro’s? Granted I don’t use the commercial modules so I have no idea what kind of dialplan those will generate for most things but I’ve been through the latest release of v14 dialplan and have pretty much migrated all the core Marco() functions to GoSub(). It’s a bit time consuming but why isn’t this already done with v15 dialplan? Why does this still sound like it’s going to be a work in progress even after the release of v15 stable? It just doesn’t make any sense to me.

Hi Tom:

From my original post:

There are supported methods for adding custom headers but they differ depending on call direction, FreePBX version and channel driver. There may or may not be support for removing/modifying headers, again depending on channel driver and FreePBX version. It is complicated enough that when the time comes it will be another long post, and will necessarily require the information here as a requisite.


This is not as easy as you propose. Since FreePBX is open source and since you’ve already done all the work why don’t you submit a few pull requests.


That won’t be possible because I didn’t touch the FreePBX code to do this. I fully and openly admit that I use FreePBX in a way that it was not designed for and intended for. I multi-tenant it and FreePBX is there to do the lowly grunt work. My modifications would not fit the project as is.

I have to know, did my support ticket inspire this post?

No. As stated, the community needs a good resource for hacking SIP headers, but to get there a bit of background is needed first.

Damn it Lorne, I wanted to feel special.

1 Like

Mr D, you are special! Just like everyone else.

1 Like

Lorne giveth, and Lorne taketh away.

1 Like


I am currently trying this.

Two questions:

1- Can I have something like this?

exten => s,1,Noop(Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf)
exten => s,n,GotoIf($["${AMPUSER}"="5004"]?custom-stuff,s,1)
exten => s,n,MacroExit
exten => s,n(complete),NoOp(Done... Continuing call)
exten => s,n,MacroExit

exten => s,1,Noop(Stating the custom stuff process...)
exten => s,n,TrySystem(doing-stuff-here)
exten => s,n,Goto(macro-dialout-one-predial-hook,s,complete)

Or is it possible to use in [custom-stuff] last line the Return() function to return to the [macro-dialout-one-predial-hook] instead of the Goto ?
(I have tried to setup with Goto(macro-dialout-one-predial-hook,s,complete), and it gives me an error that ‘e’ or ‘t’ isn’t specified or defined in macro-dialout-one-predial-hook not sure what the error exactly was, I can go back and reproduce it to get the full error if necessary)

2- Trying to setup the below for any extension matching 1XX5X

exten => s,n,GotoIf($["${AMPUSER}"="1XX5X"]?custom-stuff,s,1)

But it seems like you cannot use X for 0-9 with the Goto if variable equals in the dialplan. Any workaround?

Thanks for your help

I think you will find that the inclusion of any ‘wild card’ X requires a _ prefix, even if the expression is otherwise ‘closed’ , asterisk is not fully regex compliant.

Thank you. I tried now:

exten => s,n,GotoIf($["${AMPUSER}"="_1XX5X"]?complete)

It still doesn’t work.

-- Executing [[email protected]:1] Set("SIP/12050-00009f40", "CHANNEL(hangup_handler_push)=hangup-handler,s,1") in new stack
-- Executing [[email protected]:2] GotoIf("SIP/12050-00009f40", "0?complete") in new stack
-- Executing [[email protected]-dialout-trunk-predial-hook:3] MacroExit("SIP/12050-00009f40", "") in new stack

What is the s,1 context? You won’t get to s,n until you pass s,1