VoIP.info Guide

Asterisk Gosub subroutines: stack, scope and channel ownership

Use Gosub and Return while keeping stack frames, arguments, return values and caller- or callee-channel execution independently observable.

Reviewed 2026-10-15Asterisk subroutine documentation and Asterisk 22 application references reviewed October 12, 2026; exact application syntax, channel behavior and available functions remain dependent on the installed Asterisk branch and modules, and no PBX, dialplan, channel, stack frame, handler, endpoint, bridge, header, call or media path was inspected or testedAdvancedDocumentation based · not lab tested

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.

/etc/asterisk/extensions.conf — isolated example only
[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.

Examples require adaptation to your topology. No live PBX or hardware testing is claimed.