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
┌─────────────┐
│ Incoming │
│ Call │
└──────┬──────┘


┌─────────────┐
│ Ring Group │
│ "Sales Team"│
└──────┬──────┘

┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Alice │ │ Bob │ │ +1 415 │
│ (user) │ │ (user) │ │ 555-1234│
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└────────────┼────────────┘


First answer wins

Creating a Ring Group

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:

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

Phone Number Members

Add an external phone number as a member:

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.

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. 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
await dialstack.ringGroups.update(
'rg_01h2xcejqtf2nbrexx3vqjhp51',
{ ignore_forwarding: true },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

Confirm External

When confirm_external is true, external phone number members must press 1 before being connected to the caller. This prevents external voicemail systems from picking up the call instead of a real person. Only phone number members are affected — extension members ring normally.

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.

// 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:

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:

// 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:

{
"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:

// 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:

RG1 → ext_a → RG1 ❌ Direct loop

Indirect loop — Ring groups cannot form a circular chain:

RG1 → ext_a → RG2 → ext_b → RG1 ❌ Indirect loop

Error Responses

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

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

For indirect loops, the error shows the circular path:

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

Allowed Configurations

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

RG1 → ext_a → RG2
RG1 → ext_b → RG2 ✓ Same target, no loop

Chains without cycles — Linear chains are allowed:

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:

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:

{
"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