Skip to main content

Ring Groups

Route calls to multiple destinations simultaneously.

Overview​

A ring group dials multiple members in parallel — all phones ring at once, and the first person to answer wins. Other ringing channels are automatically cancelled.

Use cases:

  • Sales teams — Route inbound leads to all sales reps simultaneously
  • Support queues — Ring all available agents at once
  • Failover routing — Include external phone numbers as backup
Text
┌─────────────┐
│ Incoming │
│ Call │
└──────┬──────┘
│
▼
┌─────────────┐
│ Ring Group │
│ "Sales Team"│
└──────┬──────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Alice │ │ Bob │ │ +1 415 │
│ (user) │ │ (user) │ │ 555-1234│
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└────────────┼────────────┘
│
▼
First answer wins

Creating a Ring Group​

JavaScript
const ringGroup = await dialstack.ringGroups.create(
{
name: 'Sales Team',
timeout_seconds: 30,
ignore_forwarding: false,
},
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

console.log(ringGroup.id); // rg_01h2xcejqtf2nbrexx3vqjhp51

Adding Members​

Members can be either routing targets (users, dial plans, voice apps), passed as extension, or phone numbers.

Extension Members​

Add a user to ring when the group is called:

JavaScript
await dialstack.ringGroups.addMember(
'rg_01h2xcejqtf2nbrexx3vqjhp51',
{ extension: 'user_01h2xcejqtf2nbrexx3vqjhp42' },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

Member Fan-Out​

What an extension member actually rings depends on that user's Find Me / Follow Me configuration:

  • No FMFM configured — the group rings the user's phones, including their softphone.
  • FMFM configured — the group rings the targets of step 1 only, and those targets replace the user's phones rather than ringing alongside them. An external number in step 1 rings as part of the group.

Later FMFM steps and the FMFM fallback are never run inside a ring group. When the ring window closes, the group's own Timeout Action applies instead.

This matters for a common configuration: a user whose step 1 rings their devices and step 2 rings their cell. Dialing that user directly ladders through to the cell, but a call to the ring group rings only the devices. Put the number in step 1, or add it to the group as a phone number member, to have it ring as part of the group.

Phone Number Members​

Add an external phone number as a member:

JavaScript
await dialstack.ringGroups.addMember(
'rg_01h2xcejqtf2nbrexx3vqjhp51',
{ phone_number: '+14155551234' },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

Settings​

Timeout​

The timeout_seconds setting (5-300, default 20) controls how long members ring before the call is considered unanswered.

JavaScript
await dialstack.ringGroups.update(
'rg_01h2xcejqtf2nbrexx3vqjhp51',
{ timeout_seconds: 45 },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

Ignore Forwarding​

When ignore_forwarding is true, call forwarding set on a phone itself is ignored. The device is treated as busy instead of following the forward, and the group keeps ringing its other members. This is useful when:

  • Devices have call forwarding configured but you want the ring group to control routing
  • You want to prevent calls from being forwarded outside the ring group

This setting reaches only forwarding the phone performs itself. It does not disable a member's Find Me / Follow Me or change which targets that produces, but it does apply to every leg the group dials, including a phone reached through an FMFM step-1 target. See Member Fan-Out for how a member's own routing is expanded.

JavaScript
await dialstack.ringGroups.update(
'rg_01h2xcejqtf2nbrexx3vqjhp51',
{ ignore_forwarding: true },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

Confirm External​

When confirm_external is true, external destinations must press 1 before being connected to the caller. This prevents external voicemail systems from picking up the call instead of a real person.

This applies to phone number members and an extension member's Find Me / Follow Me external targets. Extension members' own devices ring normally.

The prompt plays as soon as the external destination answers and repeats every few seconds. The person has 15 seconds to press 1: if they do, they are connected to the caller; if they don't, that leg is dropped and the rest of the group keeps ringing (and the group's timeout action still applies). The 15 seconds run from the moment that destination answers, independently of timeout_seconds — a destination answering near the end of the ring window keeps the group open until it confirms or the window expires.

JavaScript
await dialstack.ringGroups.update(
'rg_01h2xcejqtf2nbrexx3vqjhp51',
{ confirm_external: true },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

Timeout Action​

The timeout_action and timeout_target settings control what happens when no member answers within timeout_seconds. Without these, the call simply ends when the timeout expires.

Three actions are available:

  • "ring_user" — Ring a specific user after the timeout. timeout_target is the user's ID.
  • "voicemail" — Send the caller to voicemail. timeout_target is either a user (delivers to their personal voicemail) or a shared voicemail box.
  • "queue" — Overflow the caller into a call queue. timeout_target is the queue's ID.

Both timeout_action and timeout_target must be set together.

JavaScript
// Send to voicemail after timeout
await dialstack.ringGroups.update(
'rg_01h2xcejqtf2nbrexx3vqjhp51',
{
timeout_action: 'voicemail',
timeout_target: 'user_01h2xcejqtf2nbrexx3vqjhp42',
},
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

To clear the timeout behavior, set both fields to null:

JavaScript
await dialstack.ringGroups.update(
'rg_01h2xcejqtf2nbrexx3vqjhp51',
{ timeout_action: null, timeout_target: null },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

Routing to a Ring Group​

To route calls to a ring group, create an extension that targets it:

JavaScript
// Create extension 200 that routes to the ring group
await dialstack.extensions.create(
{
number: '200',
target: 'rg_01h2xcejqtf2nbrexx3vqjhp51',
},
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

To send a phone number's inbound calls to the ring group, set the ring group as the number's routing target with POST /v1/phone-numbers/{phone_number_id}/route. To use it in a dial plan, see below.

Using in Dial Plans​

Ring groups can be used as targets in dial plan internal dial nodes:

JSON
{
"id": "sales",
"type": "internal_dial",
"config": {
"target": "rg_01h2xcejqtf2nbrexx3vqjhp51",
"timeout": 30,
"next": "voicemail"
}
}

If no one answers, the dial plan continues to the next node. The ring group's own timeout_seconds governs unless the node sets timeout_override; see internal_dial.

Example: Sales Team​

Complete example creating a sales team ring group:

JavaScript
// Account context for all operations
const acct = { dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' };

// 1. Create the ring group (confirm_external prevents external voicemail pickup)
const ringGroup = await dialstack.ringGroups.create(
{ name: 'Sales Team', timeout_seconds: 30, confirm_external: true },
acct
);

// 2. Add team members
await dialstack.ringGroups.addMember(ringGroup.id, { extension: 'user_alice_...' }, acct);
await dialstack.ringGroups.addMember(ringGroup.id, { extension: 'user_bob_...' }, acct);

// 3. Add external cell phone as backup
await dialstack.ringGroups.addMember(ringGroup.id, { phone_number: '+14155551234' }, acct);

// 4. Create extension to route calls to the ring group
await dialstack.extensions.create({ number: '200', target: ringGroup.id }, acct);

// 5. Route the main line to it: POST /v1/phone-numbers/{phone_number_id}/route
// with { "routing_target": ringGroup.id }

Loop Prevention​

Ring groups can contain extensions that target other ring groups or dial plans, enabling complex routing scenarios. However, circular references are prohibited to prevent infinite loops.

Prohibited Configurations​

Direct loop — A ring group cannot contain itself as a member:

Text
RG1 → member → RG1 ❌ Direct loop

Indirect loop — Ring groups cannot form a circular chain:

Text
RG1 → member → RG2 → member → RG1 ❌ Indirect loop

Error Responses​

When a loop is detected, the API returns a 422 Unprocessable Entity status:

JSON
{
"error": "ring group cannot contain itself as a member"
}

For indirect loops, the error shows the circular path:

JSON
{
"error": "rg_01xxx → dp_02yyy → rg_01xxx"
}

Allowed Configurations​

Multiple members targeting the same ring group — Parallel references are allowed:

Text
RG1 → member A → RG2
RG1 → member B → RG2 ✓ Same target, no loop

Chains without cycles — Linear chains are allowed:

Text
RG1 → member → RG2 → member → RG3 ✓ No circular reference

Mixed chains — Ring groups and dial plans can reference each other as long as there's no cycle:

Text
RG1 → member (dial plan) → DP1 → internal_dial → RG2 ✓ No circular reference

Depth Limit​

Routing nesting is limited to a maximum depth of 20 levels. Configurations exceeding this limit are rejected to prevent performance issues:

JSON
{
"error": "routing nesting exceeds maximum depth of 20"
}

In practice, most deployments use 2-3 levels of nesting at most.

API Reference​

  • Ring Groups — Create, update, and manage ring groups
  • Extensions — Assign ring groups to extension numbers
  • Dial Plans — Use ring groups in call routing flows