#Recipe: Inbound routing
Each number has an inbound rule: an agent, a ring group (simultaneous or sequential), voicemail
(recording → recording.ready with kind: "voicemail"), or webhook-decides: the platform sends
call.incoming synchronously to the rule's webhook endpoint and waits at most 2 seconds for a JSON
answer like:
{ "route": { "type": "agent", "agent_ref": "user-123" } }
Other routes: { "type": "ring_group", "ring_group_id": "..." }, { "type": "voicemail" },
{ "type": "reject" }. No (valid) answer in time → the rule's fallback_rule_id is used.
#TypeScript (SDK)
// Route a number: to an agent, a ring group, voicemail, or let your CRM decide per call.
import { createVoiceClient } from '@voice/sdk';
const voice = createVoiceClient({ baseUrl: process.env.VOICE_API_URL!, apiKey: process.env.VOICE_API_KEY! });
const { data: agent } = await voice.GET('/v1/agents/{id}', { params: { path: { id: process.env.DOCS_AGENT_REF! } } });
const { data: group } = await voice.POST('/v1/ring-groups', { body: { name: 'Sales (docs)', strategy: 'simultaneous', agent_ids: [agent!.id], ring_timeout_seconds: 25 } });
const { data: rule, error } = await voice.POST('/v1/inbound-rules', { body: { name: 'Sales line (docs)', target: { type: 'ring_group', ring_group_id: group!.id } } });
if (error) throw new Error(error.error.message);
// Attach the rule to a number.
const { data: number } = await voice.PATCH('/v1/numbers/{id}', { params: { path: { id: process.env.CALLER_ID! } }, body: { inbound_rule_id: rule.id } });
console.log(number!.e164, '→ rule', rule.name);
#Notes
- The
call.incomingevent is also delivered asynchronously to other endpoints subscribed to it (not twice to the deciding endpoint).
This example is executed against the sandbox in CI (tests/e2e/specs/m6-docs.spec.ts).