How to access B-leg of Dial() launched in [from-internal-custom] external_replaces extension?

Hi Team,

FreePBX with Asterisk 18.14.0, having a trunk outbound dial hook in place Tb(dialout-trunk-predial-hook^s^1). This outbound hook works perfectly fine in general.

Except when I am handling a SIP REFER via the external_replaces extension in [from-internal-custom] (included from [from-internal-xfer]) by executing a Dial(). The new call is made fine, but the above outbound dial hook is not getting executed for it. Is there a particular reason? How to access the “B-leg” of such a call?

This is a “replaces” REFER, so I am aware that there is in fact a B-leg existing somewhere (not on this PBX though, which is why the Dial() in the first place), but I still wish to access the B-leg generated on this PBX, as I need to manipulate SIP headers on it.


I don’t understand the technical details you mention. Please describe the big picture, including how two PBXes are involved. I think you mean “When I blind transfer an existing call to an external destination, the predial hook fails to get executed for the new call.” Is that correct? I assume this couldn’t happen on an attended transfer, because the system doesn’t even know it will be a transfer until after the call is established.

Are you somehow trying to command the ‘remote’ PBX to execute the transfer?

Hi Stewart1,

thanks for following this up.

Ok, the context is that I have a call on PBX1 set up between two trunks: a PSTN trunk where calls typically come in, and another SIP trunk where I have an application receiving and initiating SIP calls. At one point during this call, the application launches a totally separate call – to another PBX, let’s call it PBX2. When this new call is connected via PBX2, the application sends a REFER to PBX1 on the original call, with a replaces header containing the call details of this new call on PBX2. PBX1 obviously can’t fulfill the REFER request itself as the target call is not handled there, but on PBX2, so it triggers the external_replaces extension of the [from-internal-xfer] context. This is where I have a Dial() to forward the REFER to PBX2 (using a PJSIP trunk between PBX1 and PBX2).

The goal here is that even though PBX1 and PBX2 remain in this call, connecting the end parties, but the application is removed.

This works, but I do not find my outbound trunk dial hook being triggered in this very specific scenario.

Is this intentional / as designed?


Asterisk is a ‘back to back user agent’ to 'refer a call it must be done (and honored) by the A leg but PBX2 only knows about PBX 1’s B leg to refer it.

Hi dicko,

I am sorry I did not get your response, and how it relates to my outbound trunk dial hook not getting triggered? The refer is handled by PBX1 first, and using the Dial() in the mentioned context actually generates an INVITE to PBX2 with the replace info, and PBX2 can perform the replace operation just fine.

My issue is the outbound trunk dial hook not getting triggered on PBX1.


But PBX1 can’t ‘replace’ the call on the bridged side (it’s own B leg to it’s A leg) , you would need a ‘proxy’ like situation to do that

The OP Is only manipulating one leg, so the fact that the other leg is back to back across the Asterisk core doesn’t matter. Asterisk does provide some support for REFER/Replaces generating a real INVITE/Replaces, when the session doesn’t exist locally, although I seem to remember it is quite clunky, and requires special tactics in the dialplan. Moreover, if I understand the clarification, the OP has got that to work.

Where he seems to have a problem goes outside my area of expertise, as I think it relates to running certain hooks from the FreePBX dialplan, and I think the INVITE/Replaces logic is bypassing those, although I’m not too clear on that. I think for me to really understand their problem, although still, probably without being able to solve it, I think the OP needs to describe normal processing and then explain where they expect it to fit in with the REFER to INVITE/Replaces logic they are using.

I would, though, speculate that very few Asterisk users do such transfers, and it would be more than proportionally rarer for FreePBX users. When you add in their further requirement, I suspect they are unique.

Hi david55,

you more or less nailed my problem description, sorry if I could not explain it in a more straightforward way.

Effectively, our application is using 2x or 3x PBX-es for high availability reasons. Each of these PBXes have multiple trunks to PSTNs/ITSPs. The application itself is using a SIP trunk to these PBXes as well, for various historical reasons. There are calls coming in to the application via one of these PBX-es that the application can decide to blind transfer to somewhere else (to a PSTN/ITSP public endpoint, not homed on any of these PBXes) using any one of these PBX-es – not necessarily the same one that is handling the original inbound call. We simply use SIP trunks between these PBXes for such scenarios, so the application can excuse itself out of such transfer calls, while the original caller talks to that other party with the PBX1 and PBX2 in the loop.

I would have expected this scenario to be fairly widespread and generic in the business world, to be honest. Though might not be common in single-PBX SOHO environments for sure. Is this a scenario not supported/endorsed by SIP RFCs?


Attended transfer is supported by SIP. Standard Asterisk supports it as a UAS, but not as a UAC, but that is OK, as you are initiating the transfer, so Asterisk is the UAS.

What I think is unusual is that you are setting up the destination leg as a direct SIP peer to peer operation. I’d suggest that the vast majority of Asterisk users use it as though it were a simple PABX, with the domain part of URIs being irrelevant, as transfers will be directed to the user part only, and treated as though it were a phone number.

Even in complex systems, which support transfers and de-tromboning (Asterisk cannot easily take the signalling path out of a call routing), I think the transfer request normally comes from a phone, which is only connected to one switch, or is seen as a routing a de-tromboning operation, handled by the switch itself.

It further seems that you want not only Asterisk, but the FreePBX dialplan to handle this in a particular way, and I’d suggest most FreePBX users don’t even realise that REFER=> INVITE/Replaces is a feature of SIP.

(For established calls, Asterisk only supports de-tromboning of media, by the direct media Re-INVITE mechanism. Most FreePBX users use features that don’t even allow optimisation of media paths. Asterisk can initiate blind transfers before answer, although the last time I looked at, my impression was that the Transfer application implementation was a bit fragile and rarely used.)

The mechanism you seem to be describing is the attended transfer one (although I believe some phones actually do their blind transfers by doing an attended transfers and issuing the REFER the moment the target answers).

You might be correct david55, as the call made to the external party indeed gets connected before the REFER/replaces is sent by the app.

I fully accept the notion that my use case is not widespread in single-PABX environments. I was surprised this does not come up more frequently in more enterprise-y situations, with apps controlling calls among multiple PBX units. I fully assumed this was a normal design.

Understanding that triggering the FreePBX outbound trunk dial hooks won’t work in this scenario, would there be any other means to trigger a dialplan context for the B-leg of the INVITE/replaces call on PBX1?

If not, I might still be OK doing whatever needs to be done in terms of SIP header manipulations on PBX2, so not that big of a deal. I actually assumed I misconfigured something as the hook did not get triggered.

It’s not a normal design. I think this is the third or fourth time I’ve ever seen it come up in practice, at least that has caught my attention.

Ok, I used the word “normal” in a loose manner, my bad. :smiley:

Not being frequent does not make it abnormal I guess? Or would you see any reasons this would be a no-go apart from no outbound trunk dial hook getting triggered?

I don’t know. You’re in off-nominal seldom used little knowledge area for me and others.

1 Like

I’ll keep updates on how this goes, then.

BTW, this is where I got the idea from:


I see several possible solutions; if none are suitable please describe the business logic in more detail and we may have other ideas.

  1. When ready to bridge the call between PBX A and PBX B, don’t ‘transfer’ but simply send re-invites to both systems so that media flows directly between them, while your app remains in the signaling path. This should require negligible additional horsepower; the app would normally see only a BYE at the end of the call.

  2. When establishing the outbound leg, send the call to PBX A, using a prefix that causes it to route via a trunk to PBX B, which in turn calls the PSTN destination. You can then do a ‘regular’ transfer (REFER) on PBX A. This should not degrade your HA goals, because PBX A must remain functioning anyhow.

  3. With your present setup, I assume that the outbound leg on PBX B does run any predial hooks required by the destination trunking provider. While the remote transfer won’t run predial hooks for the call between the PBXes, you should be able to design the system e.g. with a custom context on the receiving end of the intersystem trunk, so that custom SIP headers are not required.

Hi Stewart1,
many thanks for the ideas and insights!!

It is a business goal to minimize the application’s time on any calls, due to its call (signaling + voice) channel pool being a precious asset (license bound). So if in option 1 you actually mean to keep the app in the loop even when the scenario calls for the original caller and the external party be joined – the app can be eliminated from the call --, then that is undesired.

As for option 2, this would require the app to care for which PBX it sends out calls to (having ‘states’ associated to PBX-es). This is also undesired. The app does not care which PBX handles what calls for it, or which one is up or down for whatever reason, it simply finds one using a HA distribution algorithm (roundrobin) for whenever it wants to raise a call, which obviously can result in the described situation where calls on separate PBXes need to be joined on occasions. And it works pretty well, except for the transfers.

Your option 3 is the one I was also thinking of, unfortunately it seems that the ‘special’ INVITE with the replaces header that the Dial() app crafts in the external_replaces context (in response to the REFER/replaces it gets from the app), the target PBX (PBX2 or PBX B) simply does not trigger any dialplan context at all… that is probably worth another topic in here?

Once the INVITE/Replaces reaches its destination, only the Replaces part is actually used. That’s the same for REFER/Replaces, when it can be handled without forwarding. The call is re-bridged without any dialplan execution.

As far as PBX1 is concerned, the original incoming call is still running on its original dialplan. I think the all on PBX2 will still be running on the dialplan that was started by the call from your special device. It pretty much has to be, as it is there has to be a dialplan thread that owns the call. In both cases, I don’t believe the Dial() application will be affected; it’s just that when it finally exits, if you have the g option set, it will find that the name of the channel has changed (although I think the unique ID and the underlying data structure are unchanged).