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 extensions (users, dial plans, voice apps) 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 SIP endpoints plus their WebRTC softphone.
  • FMFM configured — the group rings the targets of step 1 only, and those targets replace the user's endpoints 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, SIP 302 redirects (call forwarding) from devices are 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' }
);

Then assign that extension to a phone number, or use it in a dial plan.

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_id": "rg_01h2xcejqtf2nbrexx3vqjhp51",
"timeout": 30,
"next": "voicemail"
}
}

If no one answers within the timeout, the dial plan continues to the next node.

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. Assign to a phone number
await dialstack.phoneNumbers.update('pn_main_line_...', { extension: '200' }, acct);

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 → ext_a → RG1 ❌ Direct loop

Indirect loop — Ring groups cannot form a circular chain:

Text
RG1 → ext_a → RG2 → ext_b → 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 → ext_a → RG2
RG1 → ext_b → RG2 ✓ Same target, no loop

Chains without cycles — Linear chains are allowed:

Text
RG1 → ext_a → RG2 → ext_b → RG3 ✓ No circular reference

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

Text
RG1 → ext_a (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