> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# First IVR via the Visual Builder canvas

> A guided tutorial — from a blank canvas to a published IVR flow that answers your DID. Compose nodes on the builder, tag speech intents, simulate a scripted caller, publish a live snapshot, and wire it to a phone number.

The Orbit IVR Builder is a visual canvas where you drag, connect, and configure every turn of an inbound call — no JSON graph to hand-author. This tutorial takes you through one end-to-end loop: blank canvas, compose a working billing-lookup flow, validate it in the simulator, publish it, and point a DID at it. By the end you will have a flow live on a number, and you will know the lifecycle well enough to adapt it to your own routing trees.

If you are comparing the canvas to writing the IVR DSL in code, read [Pick your IVR surface: canvas or DSL](/guides/ivr-builder-canvas-vs-dsl-walkthrough) first — it puts the two surfaces side-by-side and walks the export/import round-trip. This tutorial stays on the canvas.

## 1. Open the IVR Builder

In the dashboard, go to **Voice → IVR Builder**. The page loads an empty canvas with a toolbar across the top and a node palette on the left. From the toolbar, the main actions you will use across this tutorial:

* **Save** writes the current canvas to the draft. Nothing a caller can reach.
* **Validate** runs the structural checks described in section 4. Fix issues until it comes back clean.
* **Publish** promotes the draft to the live snapshot that your DIDs actually answer.
* **Simulate** opens the in-product simulator so you can walk a scripted caller through the flow.
* **History** lists every published version, with a one-click revert.

The palette holds every building block. Hover any entry to see its one-line description. For the full node catalog and the corresponding DSL payloads, see [Build and ship your first IVR flow](/guides/build-ivr-flow). We will use only the nodes the billing-lookup example needs.

## 2. Set up: create and name your first flow

Click **New flow** on the toolbar, then **Save**. Choose a name — `Billing lookup` — and save. The canvas is blank and the flow is a draft: you have a `{ nodes: [], edges: [] }` skeleton the validator will reject until you add a start node.

You can also hydrate a flow from one of the JSON-LD templates in the template catalog — open the **Load** menu and pick a template, and the canvas populates with a pre-built graph you can edit. For this tutorial we will build from scratch so you know every node and edge intimately.

Every save you make from now until you publish only changes the draft. The toolbar shows an **Unpublished changes** chip whenever the draft differs from the live snapshot.

## 3. Compose the billing-lookup flow

We will build a flow that greets the caller, gathers their account number by DTMF, checks it against a data dip, routes valid accounts to a billing agent queue, offers an SMS callback on timeout or no-match, and hangs up. Here is the composite view of what you will end up with — twelve nodes, each one dragged from the palette and wired on the canvas.

### 3.1 Drop the entry point

Drag **Start** from the palette onto the canvas. Every flow needs exactly one entry — the validator will flag a flow with zero or two start nodes. The Start node has one outgoing handle; every other node you drop will connect from it or from a downstream node.

### 3.2 Greeting — Play Audio

Drag **Play Audio** from the palette and drop it below Start. Drag a wire from Start's outgoing handle onto the Play Audio node. Click the node to open the right-hand config panel and set the TTS text:

```
Welcome to billing support. Please have your account number ready.
```

The TTS text is what the caller hears. You can also supply an `audioUrl` — a hosted WAV or MP3 — instead of text, or use both together (TTS as a fallback when the URL is unreachable).

### 3.3 Gather the account number — Start Gather

Drag **Start Gather** from the palette and wire it from the Play Audio node. Click the node and configure:

* **Prompt**: `Enter your 6-digit account number, followed by the hash key.`
* **Max digits**: `6`
* **Finish on key**: `#`
* **Timeout (seconds)**: `5`
* **Number of attempts**: `3`

The gather node is a DTMF collector — it plays the prompt, waits for digits, and branches on the outcome. It exposes three handles:

| Handle | Condition |
| - | - |
| `gathered` | The caller entered digits and pressed # (or max digits were reached). |
| `timeout` | The caller was silent through the prompt window. |
| `nomatch` | The caller entered digits that did not satisfy a validation rule. |

Wire the `gathered` handle to the next node (the Data Dip, once you add it). The `timeout` and `nomatch` handles will go to the Offer SMS Callback node later.

### 3.4 Verify the account — Data Dip

A **Data Dip** node queries your CDP or CRM in real time during the call. Drag one from the palette, wire it from the gather node's `gathered` handle. In the config panel, set:

* **Dip type**: `Lookup`
* **Lookup key**: `account_number`
* **Value**: `{{steps.gather_account.digits}}` — a template variable that resolves to the digits the caller just entered.
* **Segment**: the CDP segment that contains your account records.

The Data Dip returns a match or no-match. It exposes two handles: `matched` and `not_matched`. Wire `matched` to the Billing Queue node (next). Wire `not_matched` to a Play Audio node that says `"We could not find that account number. Please try again or wait for an agent."` and then connect it back to the gather node for a retry, or to the Offer SMS Callback.

For a production billing flow you would also use the Data Dip to fetch the caller's current balance, open cases, and account tier, then branch the routing — VIP accounts to a priority queue, standard accounts to the main queue. The dip runs before the queue node so the data is available for screen-pop when an agent answers.

<Warning>
  A Data Dip queries the live CDP at call time. A dip that times out or returns an error falls through the `not_matched` handle — design the timeout path so a transient CDP blip does not strand the caller. For the billing flow, wire `not_matched` to the billing queue as a safe fallback rather than hanging up.
</Warning>

### 3.5 Route to the billing agent queue

Drag a **Queue** node from the palette and wire it to the Data Dip's `matched` handle. In the config panel, pick the billing ACD queue — the dropdown lists every queue you have configured on the workspace. The caller hears hold music (your configured queue audio) until an agent picks up.

The Queue node is a terminal for the caller — the call stays in the queue until an agent answers, the caller hangs up, or the queue overflows (at which point the overflow handle activates — wire it to voicemail or Offer SMS Callback).

### 3.6 SMS deflection — Offer SMS Callback

When the caller times out at the gather node or the account lookup fails three times, offer an SMS callback instead of keeping them on hold. Drag an **Offer SMS Callback** node from the palette and wire the `timeout` and `nomatch` handles from the Start Gather node to it.

In the config panel, set:

* **Message template**: `"We could not verify your account over the phone. Tap here to continue over SMS: {{callback_url}}"` — the `{{callback_url}}` placeholder is resolved at send time to a deep link that opens a two-way SMS conversation in the customer's messaging app.
* **Sender number**: pick a registered SMS sender.

The node exposes two handles: `accepted` and `declined`. Wire `accepted` to a **Hangup** node (the SMS session takes over). Wire `declined` to the billing queue as a last-resort human handoff, or to a voicemail node.

<Info>
  The SMS callback session is a two-way thread — the customer can text back with their account details, and the agent responds from the dashboard inbox. See [Queue appointment booking](/guides/queue-appointment-booking) and [Callbacks scheduling](/voice/callbacks-scheduling) for the full callback lifecycle.
</Info>

### 3.7 Dial node with geo-routing

If your billing team is split across regions, a **Dial** node can route by geography. Drag a Dial node, wire it from the Data Dip's `matched` handle (as an alternative to the Queue). In the config panel, enable **Geo-routing** and select a routing policy:

* **Round-robin** across all billing agents.
* **Closest-to-caller** based on the caller's ANI area code.
* **Language-match** based on the caller's detected locale from the `language` header on the inbound SIP invite.

The Dial node dials the agent's extension or external number. An answered call bridges caller and agent; an unanswered call falls through the `no_answer` handle, which you can wire to the queue or voicemail.

<Warning>
  A Dial node whose destination is a raw SIP URI is rejected by the validator with `transfer_external_sip`. Outbound calls terminate on the Devotel network — route by E.164 number or on-net extension.
</Warning>

### 3.8 Hangup and return-to-IVR

Every terminal path in a flow must end at a **Hangup** node, a Queue, a Transfer, a Voicemail, a Ring Group, or a Dial by Name node. Add a Hangup node for the SMS-accepted path and for any dead-end fallback. Wire the `accepted` handle of Offer SMS Callback to Hangup.

A flow can also loop back into itself — after a Data Dip fails and the caller hears the retry prompt, wire back to the Start Gather node. The validator checks that every re-entry has a route to a terminal within the cycle so the caller is never trapped in an infinite loop. The canvas highlights cycle edges in red when the backend returns a `cycle_without_exit` issue.

### 3.9 Full composite

At this point your canvas should have these nodes wired:

```
Start → Play Audio → Start Gather
  ├─ gathered       → Data Dip
  │   ├─ matched        → Queue (Billing)
  │   └─ not_matched    → Play Audio (retry prompt) → Start Gather
  ├─ timeout        → Offer SMS Callback
  │   ├─ accepted       → Hangup
  │   └─ declined       → Queue (Billing)
  └─ nomatch        → Offer SMS Callback
```

Auto-layout (toolbar button) will redraw this as a readable tree. Save, and then validate.

## 4. Tag nodes with intents, DTMF, and speech recognition

A Start Gather node is DTMF-only: it listens for keypad digits through the Jambonz gather verb. DTMF is the right tool when the answer space is small and constrained — account numbers, PINs, a "press 1 or 2" menu. It works on every phone, from a landline to a VoIP softphone, with no model round-trip and no confidence threshold to manage.

When you want the caller to speak freely — "I need to check my bill" or "someone overcharged me" — replace the Start Gather with a **Speech Input** node. The speech node transcribes the caller's utterance (via Deepgram by default, configurable per node to Google, Microsoft, AWS, or the cluster default), runs it against per-node speech intents or per-tenant intent buckets, and branches by matched intent name.

| Scenario | Pick | Why |
| - | - | - |
| Collect a numeric ID: account number, PIN, order number | DTMF (Start Gather / DTMF Input) | Deterministic digit collection, no recognition error, every phone supports it. |
| Short fixed menu: 1–4 options, rarely changes | DTMF (Menu node) | Press-1 menus are fast and unambiguous. |
| Open-ended routing: "I have a billing question" vs "I want to upgrade" | Speech Input with intent routing | One utterance routes to the right queue without a multi-level tree. |
| Accessibility or hands-free callers | Speech Input | Spoken requests beat keypad entry when the caller cannot reach the keypad. |
| PCI card capture | DTMF Input with PCI mode | Digits are collected in a DTMF-only gather that never enters Orbit's audio path. See [IVR unattended PCI capture](/guides/ivr-secure-payment-capture). |

The Speech Input node's right-hand panel becomes a speech-grammar editor: add intents by name, list phrases (one per line), and optionally assign a DTMF digit per intent for keypad fallback. The `dtmfFallbackEnabled` toggle turns keypad fallback on or off — with fallback off, the gather accepts no digits and the menu is speech-only. For the full NLU routing pipeline — modelling intent buckets, testing utterances for confidence, and wiring the fallback edge — see [Build an IVR flow with NLU intent routing](/guides/ivr-routing-nlu).

If your flow uses both DTMF and speech, a common pattern is a Menu node for the first split ("press 1 for billing, 2 for support") and a Speech Input node on each branch for the sub-route ("tell us what you need").

## 5. Validate and simulate before you publish

### 5.1 Validate the graph

Click **Validate** on the toolbar. The validator checks:

* Exactly one entry node.
* Every node reachable from the entry.
* Every branch reaches a terminal (Hangup, Queue, Transfer, Voicemail, Ring Group, Dial by Name, or Offer Callback accepted).
* No unreachable islands.
* No cycles without a route to a terminal.
* No duplicate or malformed DTMF keys.
* No raw-SIP-URI transfer targets.

Fix every issue the panel reports — **Publish** is blocked while issues persist. Click any issue line to focus the canvas on the offending node or edge.

### 5.2 Simulate a scripted caller

Click **Simulate** on the toolbar. Add turns — each turn with digits, speech, or an intent — and the walker steps through the graph, lighting up the path a real call would take and reporting the reached nodes, the handle taken at each branch, the emitted verb categories, and the termination reason.

For the billing flow, exercise three paths:

**Happy path — a known account number:**

| Turn | Input | Expected result |
| - | - | - |
| 1 | `digits: "123456#"` | Gather emits `gathered`, Data Dip returns `matched`, caller lands in billing queue. |
| Termination reason | | `queue_answered` |

**Timeout then SMS accept:**

| Turn | Input | Expected result |
| - | - | - |
| 1 | Empty (no digits within timeout) | Gather emits `timeout`, Offer SMS Callback presented. |
| 2 | `intent: "accepted"` | Callback accepted, Hangup. |
| Termination reason | | `hangup` |

**Timeout then SMS decline → queue fallback:**

| Turn | Input | Expected result |
| - | - | - |
| 1 | Empty | Gather emits `timeout`, Offer SMS Callback presented. |
| 2 | `intent: "declined"` | Callback declined, caller routed to billing queue. |
| Termination reason | | `queue_answered` |

Run all three before you publish. The same three walks can be scripted through the API — `POST /api/v1/voice/ivr-flows/:id/simulate` with a `turns` array of `{ digits, speech, intent }` objects — which lets you wire the simulation into a CI check so no regression reaches your DID. The API walker uses the same edge-precedence rules as the in-product dialog.

### 5.3 Simulation limits

* **Max turns**: 50 per simulation run. A walk that exceeds this returns `turn_limit_exceeded` and stops.
* **Max steps**: 200 graph transitions per run. Deeply nested flows with many retry loops may hit this; simplify the retry count or flatten the tree.
* **Source**: by default the simulator walks the draft. Toggle the **Source** dropdown to walk the published snapshot instead, or paste a raw definition to simulate a candidate without saving it first.

If the simulation rejects with `422 IVR_FLOW_GRAPH_INVALID`, the graph failed validation — solve the validator issues first, then simulate again.

## 6. Publish and attach to a DID

### 6.1 Publish

When the validator is clean and all three simulation paths pass, click **Publish**. The draft is promoted to the live snapshot. The toolbar **Unpublished changes** chip disappears, and the published snapshot is what the runtime now uses for inbound calls.

Every publish appends a row to the version history. Open **History** to see each published snapshot with its publish time and node count. To revert, pick an earlier version and confirm — the revert restores that snapshot and publishes it as the new head, so the DID keeps answering on the reverted definition without a manual re-edit.

### 6.2 Attach to a number

Open the number you want to route (**Numbers → your number → Routing**), choose `ivr` as the inbound type, and select the billing-lookup flow from the dropdown. Save. Inbound calls to that number will now walk the flow's published snapshot.

Alternatively, from the builder toolbar, click **Assign to Number** and pick an owned number — the same routing row is written.

The attachment is immediate. To verify end-to-end without phoning in, re-run the simulator against the published source.

## 7. After the call — ACW, dispositions, and callback hand-off

When the billing call ends (the agent hangs up), the agent enters **After-Call Work (ACW)** — the post-call state where they complete wrap-up tasks before the next call rings. ACW is configured on the queue, not the flow, but here are the touchpoints you control from the builder:

### Disposition tags

Every call can be tagged with a **disposition** — a label that records the call outcome. Common billing dispositions include `account_verified`, `balance_inquiry`, `dispute_filed`, `payment_taken`, `callback_scheduled`. Tagging is done by the agent in the dashboard; the analytics surface (queue analytics, agent scorecards, call logs) then breaks down volume and duration by disposition. See [Call disposition tags](/voice/call-disposition-tags) and [Wrap-up codes](/voice/wrap-up-codes) for the configuration surface.

### Callback queue hand-off

If the Offer SMS Callback node fires and the caller accepts, the SMS session is a separate interaction thread — but it can still lead back to a voice callback. The [Callbacks scheduling](/voice/callbacks-scheduling) page covers the full lifecycle: a caller who accepted SMS can request a voice callback when an agent is free, and the callback is enqueued at the position the original call would have held.

### Queue appointment booking

If the caller needs a scheduled callback rather than an immediate one (for example, the billing queue is closed outside business hours), direct the `declined` path from Offer SMS Callback to a flow that prompts for a preferred time slot. The [Queue appointment booking](/guides/queue-appointment-booking) guide covers the scheduling endpoint and the calendar integration.

## 8. Editor behaviors to know

A few canvas-specific behaviors that change how you work day-to-day:

* **Snap-to-tree.** Dropped nodes land at the current insert point. **Auto-layout** re-sorts the tree into a readable vertical flow; **Fit view** re-frames a long canvas.
* **Hover hints.** Palette entries carry a one-line description; toolbar buttons explain **Validate**, **Auto-layout**, **Fit view**, **History**, and the unsaved-changes indicator.
* **Selection safety.** Delete/Backspace removes the selected node, but the keys are suspended while autosave is in an error state so a failed save never costs you a node.
* **Preview.** The **Preview** button opens a read-only rendering of the flow for sharing with a teammate.

## 9. Limits

| Limit | Value | What happens when exceeded |
| - | - | - |
| Max nodes per flow | 200 | The canvas refuses to add node 201. If you need more, split into sub-flows and chain them with a Transfer node. |
| Max edges per flow | 500 | The validator rejects saves beyond this. |
| Simulator max turns | 50 | The walk stops and returns `turn_limit_exceeded`. |
| Simulator max steps | 200 | The walk stops and returns `step_limit_exceeded`. |
| Preview bundle size | 256 KB | The serialized `{ nodes, edges }` JSON must stay under this — large TTS text blocks or many intents with long phrase lists contribute. |
| Version history retained | Last 100 publishes per flow | Older published snapshots are pruned. Drafts are retained for 90 days after last save. |

## See also

* [Pick your IVR surface: canvas or DSL](/guides/ivr-builder-canvas-vs-dsl-walkthrough) — side-by-side comparison and full round-trip.
* [Design IVR flows on the visual builder](/guides/ivr-visual-builder) — the canvas reference page (full palette, config panel, and toolbar).
* [Build and ship your first IVR flow](/guides/build-ivr-flow) — the full DSL/API contract the builder writes to.
* [Build an IVR flow with NLU intent routing](/guides/ivr-routing-nlu) — intent modelling, utterance testing, and multilingual routing.
* [IVR unattended PCI capture](/guides/ivr-secure-payment-capture) — DTMF card capture in PCI mode.
* [Callbacks scheduling](/voice/callbacks-scheduling) — the callback lifecycle after SMS deflection.
* [Queue appointment booking](/guides/queue-appointment-booking) — scheduled callbacks and calendar integration.
* [Call disposition tags](/voice/call-disposition-tags) — post-call outcome tagging and analytics.
* [Wrap-up codes](/voice/wrap-up-codes) — ACW codes and queue configuration.
* [Flow versions and publishing](/guides/flow-versions-and-publishing) — draft → publish lifecycle and rollback.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.