Recipe: Ring Group and Hunt Group
Goal
A small office wants a single number that reaches a team of phones. Two behaviors are common:
- Ring group (simultaneous ring): dial
3000and every phone in the group rings at the same time. The first person to pick up takes the call; the other phones stop ringing. - Hunt group (sequential hunt): dial
3001and the phones ring one after another. The first phone rings; if nobody answers within the per-leg timeout, the next phone rings, and so on down the list. Whoever answers first takes the call.
Both are built from a single bridge action whose dial string lists the
member endpoints. Two separators control the dialing behavior:
- A comma (
,) dials the legs around it simultaneously. - A pipe (
\|) dials the groups around it sequentially -- the next group is attempted only after the previous group fails or times out.
When nobody answers, the call falls through to voicemail.
Call flow:
- An internal caller dials the group number (
3000or3001). - A dialplan extension matches the number and sets the failover and timeout variables.
bridgeoriginates the member legs -- in parallel, in series, or a mix.- The first member to answer is bridged to the caller; the remaining legs are cancelled.
- If no member answers before the timeout,
continue_on_faillets the dialplan proceed to a voicemail fallback.
Prerequisites
- The
bridgeapplication and dial-string syntax -- the comma, pipe, per-leg variable, and global variable operators. - A working XML dialplan -- this recipe adds
extensions to the
defaultcontext that ships inconf/vanilla/. - Member phones registered in the user directory
-- this recipe assumes users
1001,1002, and1003exist in the default domain (the vanilla configuration ships users1000-1019). - For the find-me/follow-me variation, an outbound gateway.
Build it
Add the following extensions to conf/dialplan/default.xml, above the
catch-all Local_Extension extension so the group numbers match first. Run
reloadxml from the FreeSWITCH console (or fs_cli) after saving.
Simultaneous ring group
Extension 3000 rings users 1001, 1002, and 1003 at the same time. The
commas between the legs make bridge originate all three in parallel; the
first to answer wins and the others are hung up automatically.
<extension name="ring_group_simultaneous">
<condition field="destination_number" expression="^3000$">
<action application="set" data="hangup_after_bridge=true"/>
<action application="set" data="continue_on_fail=true"/>
<!-- Give the whole group 25 seconds to answer, then fall through. -->
<action application="set" data="call_timeout=25"/>
<action application="bridge"
data="user/1001@${domain_name},user/1002@${domain_name},user/1003@${domain_name}"/>
<!-- Reached only if no member answered (continue_on_fail=true). -->
<action application="answer"/>
<action application="sleep" data="1000"/>
<action application="voicemail" data="default ${domain_name} 1001"/>
</condition>
</extension>
call_timeout is a global ceiling for the originate. Because the legs ring in
parallel, a single timeout for the whole group is usually what you want.
Sequential hunt group
Extension 3001 rings the members in order. Each pipe-separated group is a
separate originate attempt; FreeSWITCH tries the next group only after the
current one fails or times out. A per-leg [leg_timeout=N] on each group
controls how long that phone rings before moving on.
<extension name="hunt_group_sequential">
<condition field="destination_number" expression="^3001$">
<action application="set" data="hangup_after_bridge=true"/>
<action application="set" data="continue_on_fail=true"/>
<action application="bridge"
data="[leg_timeout=15]user/1001@${domain_name}|[leg_timeout=15]user/1002@${domain_name}|[leg_timeout=15]user/1003@${domain_name}"/>
<!-- Reached only if no member answered any group. -->
<action application="answer"/>
<action application="sleep" data="1000"/>
<action application="voicemail" data="default ${domain_name} 1001"/>
</condition>
</extension>
Each leg here rings for 15 seconds. If 1001 does not answer, bridge moves
to 1002, then 1003, then -- because no group answered -- the action fails
and the dialplan continues to voicemail.
With sequential hunting, prefer the per-leg [leg_timeout=N] shown above over
a single global call_timeout. call_timeout caps the total originate, so a
30-second call_timeout across three 15-second legs would expire before the
third phone ever rings. Setting leg_timeout per group makes each step
independent.
Mixing the two: groups that ring in parallel, in sequence
The comma and pipe operators combine. Within a pipe group, commas dial those members at once; the pipe advances to the next group on failure. This rings the front desk pair simultaneously, then the manager, then voicemail:
<extension name="ring_group_then_manager">
<condition field="destination_number" expression="^3002$">
<action application="set" data="hangup_after_bridge=true"/>
<action application="set" data="continue_on_fail=true"/>
<action application="bridge"
data="[leg_timeout=20]user/1001@${domain_name},[leg_timeout=20]user/1002@${domain_name}|[leg_timeout=20]user/1003@${domain_name}"/>
<action application="answer"/>
<action application="sleep" data="1000"/>
<action application="voicemail" data="default ${domain_name} 1003"/>
</condition>
</extension>
How it works
The argument you pass to bridge is a dial string, parsed by
switch_ivr_originate.c. The two multi-leg separators have precise,
distinct meanings, verified in the originate source:
- Comma (
,) -- simultaneous (parallel). Within a single group, every comma-separated leg is dialed at the same time. The dial string is split on the comma into a set of "and" peers that are all originated together; the first leg to answer is bridged to the caller and the rest are cancelled. This is the ring-group behavior. - Pipe (
\|) -- sequential (failover). The dial string is first split on the pipe into ordered groups. FreeSWITCH tries the groups one at a time, in left-to-right order, and only advances to the next group when every leg in the current group has failed or timed out. This is the hunt-group behavior.
Precedence: the string is split by the pipe first, then each pipe group is
split by commas. So A,B|C,D means "ring A and B together; if both fail, ring
C and D together." A bare comma list with no pipe is a pure ring group; a list
of single legs joined by pipes is a pure hunt group.
Timeouts. call_timeout (set as a channel variable or as a {call_timeout=N}
global brace on the dial string) caps the entire originate. leg_timeout,
applied per leg with [leg_timeout=N], caps an individual leg. For sequential
hunting you want per-leg timeouts so each phone gets its own ring window; for a
parallel group a single call_timeout is simpler.
ignore_early_media. By default an outbound leg that sends early media
(ringback or an announcement before answering) can satisfy the originate and
suppress further progress. Setting ignore_early_media=true -- globally with
{ignore_early_media=true} or per leg -- tells bridge to wait for a real
answer (SIP 200 OK) rather than treating early media as the win. This matters
for hunt groups whose legs route to gateways or devices that play early media,
because without it the first leg's ringback can stop the hunt prematurely. The
vanilla conf/dialplan/default.xml uses this on its group-dial extensions
({leg_timeout=15,ignore_early_media=true}).
Answer and bridge behavior. bridge does not pre-answer the caller; it
bridges audio once a member answers, so the caller hears natural ringback while
the group rings. Set hangup_after_bridge=true so the caller's channel hangs up
when the bridged call ends instead of returning to the dialplan. The voicemail
fallback after the bridge action runs only when no member answered, which is
why continue_on_fail=true is required -- without it, a failed bridge would
hang up the caller instead of continuing to the next action.
For the full dial-string grammar -- endpoint prefixes, the :_: enterprise
delimiter, and global/per-leg variable braces -- see the
Dialplan Application Reference.
Variations
Enterprise ring group, then a shared voicemail box
A department number rings everyone at once for 30 seconds, then drops the
caller into the department's shared mailbox (2000):
<extension name="sales_ring_group">
<condition field="destination_number" expression="^3010$">
<action application="set" data="hangup_after_bridge=true"/>
<action application="set" data="continue_on_fail=true"/>
<action application="bridge"
data="{call_timeout=30,ignore_early_media=true}user/1001@${domain_name},user/1002@${domain_name},user/1003@${domain_name}"/>
<action application="answer"/>
<action application="sleep" data="1000"/>
<action application="voicemail" data="default ${domain_name} 2000"/>
</condition>
</extension>
Here the timeout and early-media settings are carried inline as a global
{...} brace on the dial string instead of separate set actions -- either
style works.
If you maintain the team membership in the directory rather than hard-coding
extensions, replace the comma list with a directory group:
bridge accepts group/<name>@<domain> to ring every member of a directory
group at once using each member's dial-string (see the
user directory for defining groups).
Find me / follow me to an external number
A hunt sequence that rings the user's desk phone, then their cell phone via an outbound gateway, then voicemail. The pipe makes each step sequential; the external leg is a gateway dial string (see Gateways and Trunk Registration):
<extension name="follow_me_1001">
<condition field="destination_number" expression="^3020$">
<action application="set" data="hangup_after_bridge=true"/>
<action application="set" data="continue_on_fail=true"/>
<action application="bridge"
data="[leg_timeout=20]user/1001@${domain_name}|[leg_timeout=25,ignore_early_media=true]sofia/gateway/mycarrier/15551234567"/>
<action application="answer"/>
<action application="sleep" data="1000"/>
<action application="voicemail" data="default ${domain_name} 1001"/>
</condition>
</extension>
The desk phone rings for 20 seconds; if unanswered, the call is sent out the
mycarrier gateway to the cell number for 25 seconds. ignore_early_media=true
on the gateway leg is important here -- PSTN carriers frequently send early
media (ringback, network announcements) that would otherwise be treated as an
answer and stop the hunt. If both legs fail, the caller lands in voicemail.
Replace mycarrier with the name of a gateway you have defined and registered.
The gateway must be configured under a loaded Sofia profile before this
extension will work.