How to maintain TRANSFER_CONTEXT between Attended Transfers?

Happy New Year all,

Our Attended Transfers are not maintaining the state of the original phone call. I made changes that attempt to set the transfer context to the call filename before passing the transfer context into what was originally Cisco Jabber but is now Mattermost. The previous dial plan was functioning. The dial plan with these changes is now completely broken:

 ;[mattermost-notify]
-exten   =>       _XX,1,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${CALLFILENAME}))
+exten   =>       _XX,1,Set(TRANSFER_CONTEXT=${CALLFILENAME})
+exten   =>       _XX,2,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${TRANSFER_CONTEXT}))
 exten   =>       _XX,n,Dial(SIP/${EXTEN},60,t)
-exten   =>       _1XX,1,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${CALLFILENAME}))
+exten   =>       _2XX,1,Set(TRANSFER_CONTEXT=${CALLFILENAME})
+exten   =>       _1XX,2,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${TRANSFER_CONTEXT}))
 exten   =>       _1XX,n,Dial(SIP/${EXTEN},60,t)
-exten   =>       _2XX,1,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${CALLFILENAME}))
+exten   =>       _3XX,1,Set(TRANSFER_CONTEXT=${CALLFILENAME})
+exten   =>       _2XX,2,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${TRANSFER_CONTEXT}))
 exten   =>       _2XX,n,Dial(SIP/${EXTEN},60,t)
-exten   =>       _3XX,1,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${CALLFILENAME}))
+exten   =>       _3XX,1,Set(TRANSFER_CONTEXT=${CALLFILENAME})
+exten   =>       _3XX,2,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${TRANSFER_CONTEXT}))
 exten   =>       _3XX,n,Dial(SIP/${EXTEN},60,t)
-exten   =>       _4XX,1,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${CALLFILENAME}))
+exten   =>       _4XX,1,Set(TRANSFER_CONTEXT=${CALLFILENAME})
+exten   =>       _4XX,2,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${TRANSFER_CONTEXT}))
 exten   =>       _4XX,n,Dial(SIP/${EXTEN},60,t)
-exten   =>       _5XX,1,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${CALLFILENAME}))
+exten   =>       _5XX,1,Set(TRANSFER_CONTEXT=${CALLFILENAME})
+exten   =>       _5XX,2,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${TRANSFER_CONTEXT}))
 exten   =>       _5XX,n,Dial(SIP/${EXTEN},60,t)
-exten   =>       _6XX,1,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${CALLFILENAME}))
+exten   =>       _6XX,1,Set(TRANSFER_CONTEXT=${CALLFILENAME})
+exten   =>       _6XX,2,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${TRANSFER_CONTEXT}))
 exten   =>       _6XX,n,Dial(SIP/${EXTEN},60,t)
-exten   =>       _7XX,1,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${CALLFILENAME}))
+exten   =>       _7XX,1,Set(TRANSFER_CONTEXT=${CALLFILENAME})
+exten   =>       _7XX,2,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${TRANSFER_CONTEXT}))
 exten   =>       _7XX,n,Dial(SIP/${EXTEN},60,t)
-exten   =>       _8XX,1,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${CALLFILENAME}))
+exten   =>       _8XX,1,Set(TRANSFER_CONTEXT=${CALLFILENAME})
+exten   =>       _8XX,2,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${TRANSFER_CONTEXT}))
 exten   =>       _8XX,n,Dial(SIP/${EXTEN},60,t)
-exten   =>       _9XX,1,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${CALLFILENAME}))
+exten   =>       _9XX,1,Set(TRANSFER_CONTEXT=${CALLFILENAME})
+exten   =>       _9XX,2,ExecIf($["${EXTENSION_STATE(${EXTEN}@from-internal)}"="NOT_INUSE"]?AGI(/var/lib/asterisk/agi-bin/jabber.sh,${EXTEN},${CALLERID(num)},${UNIQUEID},"${CALLERID(name)}",${TRANSFER_CONTEXT}))
 exten   =>       _9XX,n,Dial(SIP/${EXTEN},60,t)

According to our phone users, “call comes in, rings for one second, is assigned to an agent not on the call, five seconds pass, it does it again to the same agent,”

How did I set up the TRANSFER_CONTEXT wrong and how can I make it right?

Sincerely,
Patrick Seiter

You should probably show the Asterisk console output of an attempt… and further explain how TRANSFER_CONTEXT is expected to be used and what you think should happen when it is set.

Hello Joshua,

I will ask our SYS to look for logs.

This is the full context of the issue as reported in our system:

According to a previous ticket, Cold Transfers are the only way to get the expected routing of the caller’s phone number from our Asterisk links.

Conversely, an Attended Transfer instead leads to the extension of the CSR making the transfer.

These factors may also have an effect on Transcript routing.

OPS would prefer to retain the convenience of Attended transfers except they should appropriately link & transcript to the caller rather than any CSR or internal extensions.

Our goal is to set the TRANSFER_CONTEXT to the CALLFILENAME which contains the inbound phone number of the customer.

When TRANSFER_CONTEXT is set, it should maintain state between Attended Transfers so that our Customer Service Representatives do not lose the phone number of our customers.

Patrick

I’m still confused due to lack of information or detail and specific comments of your environment but I’ll state this… an attended transfer is no different than a normal phone call when initiated from the phone, until the attended transfer is actually completed.

Happy Monday,

Sorry for the late reply. We started to look into another transfer ticket that led to some crossed wires on this issue. The other ticket pertained to performing an unattended/cold transfer to an external phone line.


Now, this is our issue transferring between internal phone lines.

When we have a customer call in, we can make a cold/unattended transfer and the original customer service representative (CSR) is no longer part of the call and can no longer hear the custom (as expected). The customer has elevator music play, and the call comes into our Accounts or Gear experts, wherever we have transferred the customer. This comes up as the customer’s phone number.

However, whenever we make a warm/attended transfer where we transfer the customer from a CSR to accounts/gear experts, the CSR is still part of the call and able to talk to accounts/GE before the transfer goes through. This comes up as the CSR’s extension.

Because of this, our second-line support cannot tell if a call is coming from a customer or from someone else in the company. Additionally, we have call transcription active in FreePBX, and the transcriptions save as the CSR’s extension and not the customer’s phone number.

This is our main issue. The fact that warm transfers are coming in as the extension instead of the customer’s phone number is causing several issues in our system. Is it possible to have a warm transfer come in labeled as the customer’s phone number?

There is no difference between a normal call and a transfer call at that time, so no.

Thanks for the quick reply!

Do you have any idea why it might be coming in as the extension number instead of the customer’s phone number? Maybe we misconfigured the dialplan.

When a phone starts an attended transfer it is a normal phone call. It’s not a transfer at all. It has no association to any other call. It’s as if they’re just calling the remote party like normal. There is no configuration to overcome that, unless you build something yourself with assumptions built in.

What would a use case for ${TRANSFER_CONTEXT} be then?

Blind transfers, DTMF based transfers, some other non-phone initiated transfer things.

Hello again and good morning. Is there any way to grab the inbound phone number from the previous call and then pass that phone number into the attended transfer (i.e. the new call)?

Yes, but you have to identify the new call as actually being part of a transfer, whilst it is just an unrelated enquiry, and you have to work out which call it relates to. That means making assumptions that Asterisk cannot safely make, in the general case.

Once the attended transfer completes, assuming you are correctly set up for connected line updates, the number will be updated.

I am not entirely sure I understood what you are trying to accomplish but would using the GoSub of the dial command and set the callerid(num) and callerid(name) there suffice?

i.e. something like this:

exten => _XX,n,Dial(SIP/${EXTEN},60,tb(new-channel-context,s,1)

[new-channel-context]

exten => s,1,Set(CALLERID(num)=${CALLFILENAME})

How does this distinguish between an outgoing, or enquiry, call from the agent’s phone and the start of attended transfer sequence? That’s the fiddly part, not the actual setting of the values.

Also, if CALLFILENAME is local, you’d have to import it from the right channel, so you have to work out what that channel is, and if it is global, you have a maximum of one call at at a time.

There are two options in the Dial command, b and B and the difference is on which channel the context is executed on. b runs GoSub on the newly created channel, and B on the current channel. So if you use b() it will invoke it on the new channel dialing the agent. ( Dial - Asterisk Documentation )

Also note that you might have to set the variable using _ or __ prefix to inherit it to the child channels created. (Check Variable Inheritance - Asterisk Documentation)

The original call is on channel A (asterisk to agent). That is the only one that knows the original caller ID, and channel variables. Your Dial is run on an outgoing channel from the agent (B). That only has the agent’s caller ID, and doesn’t have any variables linking it to channel A (except that its source endpoint is channel A’s destination end point. The channel created by your Dial, will inherit from channel B, so again has no information from channel A.

What you would probably have to do is to assume that all calls from the agent were actually the start of attended transfers, and also use global variables, or AstDB field, to store information from channel A (or rather the incoming channel that is bridged to channel A), keyed by the endpoint ID of the agent, and look this up when handling an outgoing call from the agent.

You can also do a feature code transfer, in which case the caller ID is passed immediately, because Asterisk knows it is a transfer. I haven’t seen any reason given for rejecting this.

Actually I’m a bit concerned about whether this is an application I really want to help with, given the reference to call file names.

That was the thought behind setting TRANSFER_CONTEXT was that it would act as a global variable that would persist from channel A to channel B. Looks like I was wrong on that part.

What is a feature code transfer, or rather if we are using Asterisk why/how would our attended transfers not already be feature code transfers?

The CALLFILENAME is the same thought behind TRANSFER_CONTEXT, that if we create a temporary file with the initial caller’s number and our number, we could then pass that into the next call, parse the filename, and we would know who the original caller was.

If a variable is global, it is not associated with any channel, so channel A and B are irelevant.

It is a transfer initiated by sending DMTF in the speech path, using a code defined in features.conf. Your transfers are SIP native ones, which are initated by sending SIP control messages.

Hello again all,

I’m confused on how transfers constitute an entirely new phone call:

When a phone starts an attended transfer it is a normal phone call. It’s not a transfer at all.

We have Asterisk call transcription enabled for quality assurance as most call centers do. When the Asterisk call recording records a call, it is recording both the call as it begins by calling a client/receiving a call, and then we transfer the client to another department or another company.

If a transfer constitutes a new call, then wouldn’t Asterisk call recording stop recording at the moment of the transfer? Doesn’t the fact that it continues recording mean that transfers constitute a single call?

Asterisk doesn’t record a call. It records a channel. If the channel being recorded is the one that is transferred, then it will move with it. For example:

Alice calls Bob, and Bob is recorded

Alice calls Charlie

Alice attended transfers Charlie to Bob

Bob and Charlie talk, and the recording continues because it was Bob being recorded

To Bob it was the same channel throughout the entire process