Asterisk: Call Queues & Agents (Building a call-in show)

Quite some time ago I started playing with AsteriskPBX. I haven’t yet written a detailed account of how this came to be, so let me sum it up. I originally wanted to convert my house phones in to a hybrid VOIP setup. I only have an analog (FXS) port coming from my fiber terminal, despite the fact it’s technically a VoIP service. I also didn’t want to spend hundreds of dollars on cordless handsets to replace the existing collection of handsets; but I wanted the ability to be called down in the shack over ethernet since cordless DECT doesn’t reach there very well.

What transpired was a series of tangents as I needed to learn Asterisk, buy some hardware, figure out the flaws in the setup, and getting distracted doing other things. I still have a couple of ATAs down here and I’ve yet to get the house fully switched over. But in the meantime I decided to do another stupid trick and attempt to do a livestream call-in show using my deskphone to control the queue as an agent, a softphone “barging” in to the agent’s channel, and three days of trying to make it all work.

The main problem I had was the documentation I could find for anything related to this subject was based on older versions of Asterisk. You literally cannot rely on documentation for older revisions when doing Asterisk; there have been some massive changes in how things work. I spent three days trying to get this working; every time I felt like I kept running through a loop. To make a long story short, if you’re running Asterisk 18, as I am; then searching online for things related to ‘asterisk call queue agent’ will be a bit of a waste of time. It probably didn’t help that my Asterisk configuration didn’t load every single module; and several I needed hadn’t been loaded.

Anyway…after three days of stumbling around search results, reading older revision documentation in order to catch up, and pulling my hair out. Turned out reading the page about changes from chan_agent to app_agent_pool was the lone doc that made everything fall in place.

Asterisk Modules

I did not keep track of what modules I had to activate in my barebones install; I know I activated voicemail and while applications a while back; I don’t remember if db was in the minimal config. I don’t think queue requires any of those, so it’s probably a good bet these are all you’ll need to put in modules.conf:

load =
load =
load =
load =

I seem to remember in reading the documentation, you needed to make sure that app_queue as loaded after; I did this moving them to the very end of the modules.conf file. But in writing this I noticed a duplicate entry for at the top of the list; so I’m not sure anymore and I’m writing this on my birthday…so I don’t feel like looking it up again. I don’t think it’ll hurt to move pbx_config to the end of the load with app_queue coming right after. For all I know the first one could be erroring out. Loading seemed to be a big key as to why nothing was working for me.

Call Agents

I wanted the call agent function simply so I could use * and # on the deskphone in a way that felt “more radio” than punching them on a softphone. I mostly just didn’t want to have to switch apps to switch calls. This is what gave me the most trouble, mostly due to the lack of chan_local and knowing that I had to specify the agent as local…and…wow. The way agents are created is also VASTLY different than the old software. You no longer have a single [agents] context that specifies agents; each agent ID gets it’s own context.



This is my absolute minimal configuration for Agent 501. The options themselves are documented. In fact it was pretty easy for me to tell from the same conf how this worked before I read the CHANGELOG that mentioned it.

Call Queue Setup

I was so close on this one. In fact the only thing I had wrong was how I was adding agents as members. Most docs and examples made it look as easy as:

member = Agent/501

But no, that’s wrong; and it was only after reading about the change to app_agent_pool and a forum post that I figured out how to actually specify an agent as a member:

member => Local/501@agents,,Jay,Agent:501

This is why not having chan_local loaded screwed me over. I didn’t click that the agent would use a local channel. It makes total sense now, sure. But after getting chan_local loaded and converting to that line; it all started working.


Most everything I read said I could just simply call AgentLogin() and it would ask for the ID and pin; then I realized the new system didn’t use a pin for an agent….then it turns out it doesn’t work that way at all. All you simply do now is pass AgentLogin the agent ID. Where’s the authentication? You now do that somewhere in the dialplan. Since I’m the only one using this PBX, I just set up an extenstion to do AgentLogin(501) and I’m off to the races, right?


When I did this, I could get calls by pressing #, but I couldn’t hang-up. That’s because pressing * to hang up is handled by the internal functions. So I have to set the dtmf_features for the channel. This is fine and all, except AgentLogin doesn’t do that; these are usually passed in the Dial() app. This is when I found out I needed loaded; because trying the obvious thing of using Set(CHANNEL() didn’t work at first.

same = n,Set(CHANNEL(dtmf_features)=H)

The other part of this I hadn’t mentioned was using AgentRequest.

exten = _XXX,1,NoOp()
same = n,AgentRequest(${EXTEN})
same = n,Congestion()

I already had this in the dialplan when I tested; as during my digging I came across this somewhere. So my full dialplan for agents looks like this:

exten = _XXX,1,Set(CHANNEL(dtmf_features)=H)

same = n,Answer()
same = n,AgentLogin(${EXTEN})
same = n,Hangup()

exten = _XXX,1,NoOp()
same = n,AgentRequest(${EXTEN})
same = n,Congestion()

This line in my deskphone’s context logs it in as an agent:

exten = 6000,1,Goto(agent_login,501,1)

So at this point, everything was working. I call my DID from my cell phone, get in to the queue, I hear the deskphone beep, # to take call, and * to hangup. Excellent.

Let’s Build The Call In Show

Obviously if I want to have callers on a live-stream, I need to get the channel audio in to OBS and get my audio in to the softphone. Thankfully, shared mode means the softphone and OBS can share the mic. I don’t get the highly compressed audio out to the caller, but I don’t think that’s important. I originally did this to avoid latency, but I don’t think that would have mattered much either. It’s just easier to not worry about routing the calls around too much. I largely just have to configure the softphone to use my normal desktop audio (which is captured by OBS) and to share the mic. Easy peasy. Now….how do we get this thing to “barge” in on the agent’s channel?

To do this, we can use ExtenSpy. It will require you to have loaded. But other than that, we just have to tell it which extension we want to “barge” in to; barge being the option that allows the spy to hear and talk in to the channel.

exten = 777,1,ExtenSpy(501@agents,Bq)
same = n,Hangup()

It is literally that easy. Our agent’s extension is 501@agents, the B option activates barge, q means it doesn’t beep all the time. So all I have to do is dial 777 on the softphone. At that point, I can hear and talk to whatever call the agent is on. Mute the deskphone, punch keys on it.

Switching All The Lines Over

I have 3 DID (Direct Inward Dial) numbers assigned to my SIP trunk. By default, all of these drop you in to my ivr of madness. I have my deskphone setup to mimic lines; extensions basically have a pre-configured outgoing caller-id. There is some logic in the trunk’s context to send each DID to a specific extension; but the IVR itself does this based on an extension variable set in the trunk’s context. This means I can just redirect everyone to the queue by modifying just my IVR dialplan; but it also makes it stupid easy to automate this by phone so I don’t have to manually update files and reload the dialplan.

I had hoped to make everything 100% automated; and it’s possible I can still do that…just using external bash scripts. In the meantime, why can’t I just punch a number to flip everything to the queue and another number to return it to “normal”.

exten = 1000,1,Set(DB(callin/stream)=1)
same = n,Playback(hello)
same = n,Playback(goodbye)
same = n,Hangup()

exten = 1001,1,Set(DB_DELETE(callin/stream)=0)
same = n,Playback(goodbye)
same = n,Playback(hello)
same = n,Hangup()

I’m going to make use of Asterisk’s internal database because it’s easiest. When I dial 1000 from the spy’s extension; I create a key called stream under the callin family. I don’t care about the value; I’m going to work on the existence of the key, deleting it to turn the callin queue off.

same = n,GoToIf($["${DB_EXISTS(callin/stream)}" = "1"]?inbound-ivr,[callin],1:normal)
same = n(normal),Background(if-u-know-ext-dial)

exten = [callin],1,Playback(phoneintro)
same = n,Queue(callin,h)

* I have substituted [callin] for the actual extension, suckers.

If the key exists, then we basically jump to the extension for the callin queue. For the time being, I just have this mapped to an actual ivr extension on my DIDs; which allows me to pre-queue people before flipping the lines over.

Posted on: 18-DEC-2021