Use Gosub for a bounded reusable dialplan block
Asterisk recommends Gosub in place of the deprecated Macro application. A regular Gosub transfers execution to a context, extension and priority on the current channel; Return resumes at the priority after the call. That is code reuse, not a new call leg, endpoint or bridge.
Name the caller, entry point, permitted arguments, expected result and exit paths before reusing the block. Keep authorization in the calling context: a shared subroutine should not silently expose a trunk, feature code or outside destination to a channel that could not reach it directly.
Make the entry, arguments and return explicit
The subroutine receives positional arguments as ARG1, ARG2 and later values in its own frame. Return() exits the current frame; when a value is supplied, the caller can read the documented GOSUB_RETVAL. Treat an unset argument, an empty string and a value inherited from unrelated channel state as different cases.
This inert example records one argument and returns a label without dialing. The labels and values are illustrative. Verify the exact application help on the running branch before adapting it.
[rv-entry]
exten => 7001,1,NoOp(Starting bounded Gosub example)
same => n,Gosub(rv-label,s,1(${EXTEN}))
same => n,NoOp(Subroutine result is ${GOSUB_RETVAL})
same => n,Hangup()
[rv-label]
exten => s,1,NoOp(Received extension ${ARG1})
same => n,Return(checked-${ARG1})Treat every invocation as a stack frame
Nested subroutines add frames. The current context, extension and priority are not the same as the caller location while the subroutine is running. Asterisk documents STACK_PEEK() for inspecting the context, extension, priority or label that called a Gosub at a chosen frame depth. Use that as diagnostic evidence, not as a substitute for a clear interface.
Every normal path that is meant to resume the caller needs an explicit Return. A Goto, fall-through, invalid target or hangup can leave the intended return path. Draw conditional branches and error exits, then confirm the loaded priorities rather than assuming source-file order is the runtime path.
Choose the channel before choosing the handler option
A plain Gosub executes on the current channel. Asterisk pre-dial handlers add two distinct Dial() locations: B() runs on the caller channel before callee channels are created, while b() runs on each created callee channel before dialing. Those owners are not interchangeable for channel variables, identity or PJSIP headers.
A pre-bridge U() handler runs on the called channel after answer and before the bridge. It is later than pre-dial execution and can affect whether bridging continues. Record the exact Dial() option, channel name and handler entry; a log line from the same subroutine name does not establish which channel or lifecycle point executed it.
Keep subroutine completion separate from call completion
A returned frame proves only that its dialplan block reached Return. It does not prove that Dial() created the intended channel, the remote side answered, a pre-bridge handler completed, the bridge formed or two-way media worked. Likewise, a completed call does not prove that every conditional subroutine branch returned normally.
When a subroutine sets a header or channel variable, inspect the actual owner after channel creation. The outbound-header guide separates caller state from the callee PJSIP channel, while the Local-channel guide covers optimization and data ownership on an internal pair.
Validate one frame and one call lifecycle at a time
Load the intended dialplan through the supported configuration path. Inspect the exact entry and return priorities, then capture channel names, unique identifiers, stack evidence, arguments, returned value and the priority that resumes. Exercise missing and empty arguments, each conditional return, invalid target and nested invocation in an authorized non-production path.
For B(), b() or U(), add the relevant caller, callee, answer and bridge evidence. Roll back if the handler changes routing, identity, headers, timing, cleanup or media outside its declared boundary. No PBX, dialplan, channel, stack frame, handler, endpoint, bridge, header, call or media path was inspected or tested for this guide.
Treat a hangup handler as a channel-owned Gosub target
The hangup-handler guide applies explicit target, arguments and Return() discipline to a LIFO stack that runs during channel teardown. It does not turn the handler into a whole-call or bridge object.
Do not substitute a subroutine for a bridge-lifecycle decision
A Gosub target returns to its caller. The after-bridge guide covers the different case where a surviving channel must enter a destination after leaving its next bridge.
Use a returning callee pre-dial Gosub for the outbound offer
A PJSIP media-offer override belongs on the intended outbound PJSIP channel. The lower-case b handler runs on each callee and must return before dialing proceeds.
Use the pre-bridge Gosub for answered response headers
The PJSIP response-header guide shows why Dial() option U() runs on the answered called channel before bridging, while b() and B() run before the 200 response exists.
Keep inheritable-header pre-dial handlers short and returning
The PJSIP inheritable-header guide uses lowercase b() on each new callee and preserves the required Return() boundary.
Apply Gosub semantics to FreePBX 17 custom migrations
The FreePBX 17 migration guide maps legacy Macro targets, implicit location variables, exits and offsets onto explicit Gosub frames, arguments, local values and Return() behavior.
Keep lock cleanup inside the owning subroutine
The dialplan-lock guide keeps acquisition, protected state, release and Return behavior visible in one channel-owned control path.
Keep LOCAL values inside the intended frame
The variable-scope guide distinguishes LOCAL() and LOCAL_PEEK() stack behavior from channel inheritance, master-channel access and cross-channel reads.
Define whether a subroutine returns or raises
The dialplan exception guide keeps Gosub return contracts, custom RaiseException() reasons and idempotent cleanup separate.
Keep function-invoked subroutines bounded
The evaluated-subroutine guide adds version, argument, Return-value and evaluation-time side-effect boundaries for EVAL_SUB().
Do not treat a periodic hook as an ordinary same-channel subroutine
The periodic-hook guide preserves Asterisk's asynchronous hook-channel boundary and its documented HOOK_CHANNEL and HOOK_ID variables.
Dial handlers run on different channels and phases
The Dial guide distinguishes caller B(), per-callee b() and post-answer callee U() execution and requires each handler to return.
Originate handlers have caller and created-channel owners
The Originate guide distinguishes uppercase B() on the current channel from lowercase b() on the newly created channel and requires handler return.
Sources & applicability
Primary references for the technical details above. Operational examples and planning checklists are VoIP.info editorial guidance.
- Asterisk dialplan subroutines overview ↗
- Asterisk Gosub guide ↗
- Asterisk 22 Gosub application reference ↗
- Asterisk 22 Return application reference ↗
- Asterisk STACK_PEEK function reference ↗
- Asterisk pre-dial handlers ↗
- Asterisk pre-bridge handlers ↗
- Asterisk 22 Dial application reference ↗
- Asterisk hangup handlers ↗
- Asterisk 22 CHANNEL function reference ↗
- Asterisk 22 HangupHandlerPush AMI event ↗
- Asterisk 22 HangupHandlerPop AMI event ↗
- Asterisk 22 HangupHandlerRun AMI event ↗
- Asterisk hangup-cause overview ↗
- Asterisk 22 Hangup AMI event ↗
- Asterisk 22 Bridge application reference ↗
- Asterisk 22 Goto application reference ↗
- Asterisk 22 BridgeEnter AMI event ↗
- Asterisk 22 BridgeLeave AMI event ↗
- Asterisk PJSIP_MEDIA_OFFER function ↗
- Asterisk PJSIP_SEND_SESSION_REFRESH function ↗
- Asterisk Dial application ↗
- Asterisk PJSIP advanced codec negotiation ↗
- Asterisk res_pjsip endpoint reference ↗
- Asterisk codec modules and translation paths ↗
- Asterisk PJSIP_RESPONSE_HEADER dialplan function ↗
- Asterisk PJSIP_RESPONSE_HEADERS dialplan function ↗
- Asterisk res_pjsip header-function source ↗
- RFC 3261: SIP Session Initiation Protocol ↗
- Asterisk PJSIP_INHERITABLE_HEADER dialplan function ↗
- Asterisk 20 PJSIP_INHERITABLE_HEADER reference ↗
- Asterisk 22 PJSIP_INHERITABLE_HEADER reference ↗
- Asterisk 23 PJSIP_INHERITABLE_HEADER reference ↗
- Asterisk PJSIP_HEADER dialplan function ↗
- Sangoma: upgrading to FreePBX 17 ↗
- Sangoma: FreePBX 17 platform overview ↗
- Asterisk 20 Macro application reference ↗
- Asterisk Gosub application reference ↗
- Asterisk Return application reference ↗
- Asterisk LOCAL function reference ↗
- Asterisk LOCAL_PEEK function reference ↗
- Asterisk StackPop application reference ↗
- Asterisk LOCK dialplan function ↗
- Asterisk TRYLOCK dialplan function ↗
- Asterisk UNLOCK dialplan function ↗
- Asterisk 22 SHARED dialplan function ↗
- Asterisk global variables basics ↗
- Asterisk variable inheritance ↗
- Asterisk 22 Set application ↗
- Asterisk 22 MASTER_CHANNEL function ↗
- Asterisk 22 LOCAL function ↗
- Asterisk 22 LOCAL_PEEK function ↗
- Asterisk 22 VARIABLE_EXISTS function ↗
- Asterisk 22 IMPORT function ↗
- Asterisk special dialplan extensions ↗
- Asterisk 23 RaiseException application ↗
- Asterisk 23 EXCEPTION function ↗
- Asterisk 23 TryExec application ↗
- Asterisk 23 DIALPLAN_EXISTS function ↗
- Asterisk 23 EVAL_EXTEN function ↗
- Asterisk 23 EVAL_SUB function ↗
- Asterisk 23 PERIODIC_HOOK function ↗
- Asterisk 22 PERIODIC_HOOK function ↗
- Asterisk 20 PERIODIC_HOOK function ↗
- Asterisk Playback application ↗
- Asterisk CHANNEL function ↗
- Asterisk HANGUPCAUSE_KEYS function ↗
- Asterisk 23 Originate application ↗
- Asterisk 22 Originate application ↗
- Asterisk 23 AMI Originate action ↗
- Asterisk ARI interface overview ↗
Examples require adaptation to your topology. No live PBX or hardware testing is claimed.