RustPBX Basics: IVR — Menus, Schedules, and When to Go External
Every PBX has an IVR. The differences that matter: how flows are authored, how they branch on time and data, and how easy it is to hand a call to an external brain when menus stop being enough. Here’s the RustPBX toolbox, inside-out.
Layer 1: Built-in IVR Flows
Flows live as TOML/JSON files under config/ivr/, or authored visually with the IVR Editor addon (drag-and-drop menus, business-hours control, TTS synthesis, multi-level menus — publish straight into the PBX).

Inside the editor, each menu has its greeting (audio or TTS), timeout/retry behavior, and one card per DTMF key with its label and action — queue, transfer, sub-menu, voicemail, webhook, or hangup:

Nodes cover the classics: play prompt, collect digits, menu branching, route to extension/queue/trunk, voicemail, and record. TTS prompts synthesize through the configured engine, with PBX-wide prompt aliases so third-party trees don’t hard-code engine-specific strings.
Layer 2: Schedules Inside the Flow
Flows branch on business hours, holidays, and night mode — the same schedule model as queues:
- Business hours with timezone
- A holiday map, each with its own action
- Night mode for the daily cycle
One flow serves day and night, and the holiday map means February 17th doesn’t play your January greeting.
Layer 3: Step IVR — Your Code Decides
When menus aren’t enough — CRM lookups, AI decisions, anything dynamic — Step IVR externalizes the flow: each call step POSTs to your provider API, and your code answers with the next action (play, collect, bridge, transfer, hangup):
[ivr.provider]
url = "http://10.0.0.50:8080/ivr/step"
timeout_secs = 10
The protocol handles the hard parts: DTMF with timeouts, voice recognition results, recording completion, and a resume event so a flow suspended by a consult transfer continues exactly where it stopped — no menu replay.
Layer 4: The Safety Net
External providers fail. [proxy.ivr_fallback] routes the call into a local IVR when the provider can’t continue — rules matched with dialplan semantics, first match wins:
[proxy.ivr_fallback]
default = "default"
[[proxy.ivr_fallback.rules]]
name = "vip"
priority = 100
match = { "from.user" = "^9" }
target = "builtin_vip_step"
Callers never hear a failure; they hear a menu.
Layer 5: Full AI
Beyond step-by-step external logic, the realtime AI-voice bridge connects LLM agents to calls as a peer participant — see AI voice bots with human handoff for that pattern.
Guides: Step IVR and the IVR Editor.