Part 1 of 2.
The Symptoms Everyone Recognizes
Anyone who has shipped an IVR knows the pattern. Requirements go missing: gaps that don’t surface during design review, but that turn up during implementation or, worse, during UAT. Dates slip, not because the underlying work is inherently hard, but because the way the system was specified manufactures complexity that compounds during build. QA can’t test features in isolation. The system was never decomposed into independently testable units, so testing collapses into end-to-end-or-nothing scenarios that catch defects late and expensively.
These three pains are not independent. They have a common cause, and naming it precisely is the point of this article.
The IVR design document, the Word file that anchors nearly every IVR project in the industry, is asked to serve business stakeholders, UX designers, developers and QA simultaneously. To stay readable for its primary audience (the business), it lands at a level of detail that is insufficient for development. The gap gets filled the only way it can be: through hallway conversations, meeting decisions and Slack threads between designers and developers reconstructing what the document should have specified.
In practice, the design document functions as a guide for development, not as an actual software design document. The real spec lives in undocumented conversations.
That is the root cause. The missing requirements, the slipped dates, the untestable builds. These are what the guide-vs-spec gap produces downstream.
This article (Part 1 of two) diagnoses how that gap manifests at the implementation layer. Part 2 will lay out what to do instead.
The Anatomy of the Typical IVR Design Document
To diagnose the problem precisely, it helps to look at what these documents actually contain. The specifics vary, but the shape is remarkably consistent across organizations, vendors and decades.
Format. A Word document, typically 50 to 200+ pages. Sometimes a PDF exported from Word. Occasionally a wiki page that aspires to the same structure. Embedded or appended Visio diagrams represent the call flow visually.
Typical contents, in roughly the order they appear:
- Document control: version history, approvers, change log
- Background and business context: why the IVR exists, what problem it solves, who the callers are
- High-level call flow narrative: prose description of the major paths, often organized by business function (checking, savings, fraud)
- Detailed flow descriptions: section-by-section walkthroughs of each menu, prompt and branch
- Business rules: eligibility logic, routing rules, hours of operation, VIP handling
- Integration notes: a paragraph or two per integration, gesturing at what data is needed from which system
- Error handling: usually a separate section, usually shorter than it should be
- Visio diagrams: appended or embedded
- Appendices: sometimes prompt inventories, sometimes glossaries, sometimes nothing
What it calls “requirements”, and what those usually are
This is where the document starts to mislead its own readers. Most IVR design documents have a section labeled “Requirements,” or treat the detailed flow descriptions as requirements. They are not, in the engineering sense.
A requirement, properly written, is singular and testable. It describes one behavior, in unambiguous language, in a form that can be verified. RFC 2119 verbiage (MUST, SHOULD, MAY) is the standard convention for removing ambiguity:
When a caller enters the welcome state, the IVR MUST play the prompt: “Thank you for calling Acme Bank. For English, press 1. For Spanish, press 2.”
That’s a requirement. One behavior, one state, one verifiable output.
What design documents typically contain instead are intent statements: prose that describes what the system should generally do, written for human comprehension rather than verification:
“After the welcome message, the caller is presented with a language selection menu, where they can choose English or Spanish.”
That sentence is readable. It is not a requirement. It does not specify the exact prompt wording. It does not specify the input modality (DTMF, speech, both). It does not specify what happens on no-input or no-match. It does not specify timing. It cannot be tested as written. Two implementations could both satisfy it and behave completely differently.
Most of what design documents present as requirements are sentences of this second kind. The actual requirements (the singular, testable ones) either don’t exist, or live in the developers’ heads after the hallway conversations described above.
The implicit audience contract
Read enough of these documents and a pattern emerges: the writing is calibrated for the business reader. It flows as narrative. It explains things. It uses prose. This is not an accident. The business is the first and most demanding consumer, the one whose sign-off unlocks the project. The document is written to be read by them, approved by them and circulated.
The downstream audiences (developers, UX, QA) read the same document, but they are reading an artifact that was optimized for a different reader. They are, in effect, secondary users of someone else’s deliverable.
What an IVR Actually Is, and Why the Document Format Hides It
An IVR is, in computer science terms, a graph. More specifically, a state machine. It has:
- Nodes: states the call can be in (a menu, a prompt, a lookup, a transfer)
- Edges: transitions from one node to another
- Transition conditions: what causes a given edge to be taken (a DTMF input, a speech match, an API response, a timeout, an account type)
This is not a stylistic observation. It is what the system is. Any correct specification of an IVR has to capture nodes, edges and the conditions under which each edge fires. Everything else is presentation.
The design document, as typically written, does represent the full graph, but it represents it generically. It describes every possible node and every possible transition condition in one flattened narrative. What it does not represent is any specific traversal through that graph.
The traversal synthesis problem
Consider a banking IVR. The document describes the universe of possible flows: checking, savings, investments, fraud, card services, disputes. A business stakeholder asks the reasonable question: “Walk me through the checking flow.”
What happens next is invisible and consequential. Someone (usually the designer, sometimes a BA, sometimes a developer) synthesizes the checking flow in their head by reading the generic document and inferring which specific transition conditions fire to produce that path. They mentally execute the state machine with a specific set of inputs and narrate the result.
That synthesis is not in the document. It cannot be extracted from the document. It exists only in the head of whoever did the walkthrough, and the next person who needs the checking flow has to do the synthesis again, possibly arriving at a different answer.
So the document fails everyone:
- Developers get a generic graph with under-specified transition conditions
- Business stakeholders get walkthroughs that feel authoritative but are ad-hoc mental simulations of an ambiguous source
- UX gets neither the full state machine nor the specific caller journeys, just prose that gestures at both
The document represents the graph. It does not represent the traversals. In IVR design, both matter. The graph is what gets built, but the traversals are what callers actually experience and what stakeholders actually evaluate.
The goto problem
There is a second consequence of representing the graph this way, and it surfaces in the code.
To make the document legible to the business audience, transition conditions are written in narrative form: “if the caller selects checking, go to the checking menu; if they select savings, go to the savings menu; if the account is flagged for fraud, go to the fraud handler.” Every transition reads as an instruction to jump somewhere.
When the developer picks up that document and implements it, the structure of the prose becomes the structure of the code. Each “go to the X menu” becomes a transfer of control to X. The resulting implementation is, effectively, a program written in goto statements, branching from node to node based on conditions scattered throughout the codebase, with no enforced structure connecting them.
This violates nearly every principle of software design we have spent fifty years learning, and it does so in a way that punishes the developer no matter which way they turn:
- Cohesion. A single logical node’s behavior (its prompt, its grammar, its error handling, its transitions) is scattered across multiple sections of the document and, consequently, multiple places in the code. The thing that is a unit isn’t represented as one.
- Coupling. Because transitions are specified as direct jumps rather than as conditions on edges leaving a node, every node ends up coupled to the specific downstream nodes it might transfer to. Worse, when developers try to consolidate repeated logic into a shared block (the disciplined move), they are forced to pass context flags inward to preserve each caller’s behavior, and the shared block ends up coupled to every caller that touches it.
- DRY (Don’t Repeat Yourself). The spec doesn’t give the developer a clean unit to reuse. They face a forced choice: duplicate the logic at each call site (visible repetition), or consolidate it behind context flags (hidden coupling, see above). Both are bad. The document doesn’t model the abstraction, so the developer can’t faithfully express it.
- Separation of concerns. Business logic, prompts, input handling and integration calls all live tangled together in the prose, and they end up tangled together in the implementation.
This is not a problem of developer discipline. A developer working from a goto-shaped specification produces goto-shaped code, because the spec doesn’t expose the structure that would let them do otherwise. The graph is there. It’s what an IVR is. But the document has flattened it into sequential prose, and the flattening is what gets built.
The shared block, viewed from the outside. Many callers feed into one consolidated handler:

Eight callers, each passing its own context flags. The shared block has done its job: there is no copy-paste. The cost shows up inside.
The shared block, viewed from the inside. It branches on context flags and exits back to whichever caller it came from:

The block knows about every flow that touches it, and every flow that touches it knows what flags to set. Adding a ninth caller means editing the shared block. Changing the prompt for one caller risks breaking another, because they share internal branches. Testing the block in isolation is impossible. Its behavior is defined by the context its callers manufacture.
This is the more honest picture of what happens in practice. Developers see the duplication coming and consolidate. That’s the disciplined move. But the design document never modeled the node as a unit with a clean interface; it specified traversals that happened to overlap. When the developer collapses the overlap, the only way to preserve every caller’s behavior is to push the differences inside via flags. The result is worse than duplication, because the coupling is hidden.
The same IVR, drawn as what it actually is:

One PlayBalance state, parameterized by the account context that reached it. One ReadTransactions state. One Continuation state. Transitions carry conditions, not destinations baked into prose. This is the structure the document had. It was flattened into narrative, lost in translation and reconstructed badly in code.
Where It Breaks: Concrete Failure Modes at the Implementation Layer
The failure modes below are specific consequences of the document model described above. Each one is real, each one is common and each one shows up in production IVRs that frustrate callers every day.
Ambiguous prompts and missing wording → unspecified data contracts
The typical design document captures the implicit requirement (“ask the caller for their account number”) but omits the explicit requirement that developers actually need: the account number is five digits, a dash and three alphanumeric characters.
That omission cascades:
- Speech grammars cannot be built correctly (or get built wrong and fail in production)
- DTMF input validation is guessed at
- Confirmation prompts (“I heard one-two-three-four-five dash A-B-C, is that right?”) cannot be designed
- Error recovery has nothing to validate against
What looks like a wording problem is really a data contract problem. The document captured intent but skipped the specification. Developers then either ask (slowing things down) or assume (introducing defects).
What the design document typically says:
The system prompts the caller for their account number and validates the input.
What the developer actually needs to implement that single node:

Format: ^[0-9]{5}-[A-Z0-9]{3}$. Two retry types with separate counters. Confirmation step with re-entry path. Three terminal outcomes from the lookup. None of this was in the document, but all of it has to be in the code.
Undefined error and exception paths: present but underspecified
Most design documents do address error handling. The problem isn’t omission; it’s depth. You’ll find statements like “if the caller enters an invalid account number, re-prompt”, but not:
- How many retries before escalation?
- Does the re-prompt wording change on the second attempt? The third?
- What constitutes “invalid”? Wrong format, valid format but not found, system timeout?
- What’s the no-input behavior versus the no-match behavior, and are they handled differently?
- Where does the call go on max-retry? Agent, different menu, disconnect with callback offer?
The document is operating at guide-level detail when development needs spec-level detail. The happy path gets paragraphs; each exception path gets a sentence.
A single interaction node, fully specified, has this much structure:

One happy edge. A retry sub-machine with its own counter and escalation logic. A format-violation path with a different reprompt. A platform-timeout path that bypasses retries entirely. The document specifies the happy edge in paragraphs. Everything else gets a sentence between it, if it gets one at all.
Drift between prose and diagram: two artifacts, no synchronization
The Visio diagram is a workaround. The prose document cannot convey flow visually, so someone (often not the original author) reads the Word document, opens Visio, drops in shapes and transcribes (or worse, paraphrases) the flow into boxes and arrows. Prompt text gets copy-pasted, sometimes retyped.
From that moment forward, there are two artifacts purporting to describe the same system, and no mechanism keeping them in sync.
The drift is immediate and continuous:
- The Word document gets edited in a review cycle; the Visio doesn’t
- The Visio gets corrected in a working session; the Word doesn’t
- A prompt is reworded in one but not the other
- A branch is added to the diagram that was never written into the prose
- A week later, the team is circulating last week’s Visio against this week’s requirements, and no one knows which is authoritative
Neither artifact was built to be the source of truth. The Word document wasn’t structured to be authoritative about flow; the Visio wasn’t structured to be authoritative about requirements. Each is a partial view, and the partiality is exactly where they disagree.
The root issue isn’t that people are sloppy about keeping diagrams updated. It’s that the diagram exists because the document format was inadequate, and now the team maintains two inadequate artifacts instead of one.
What the prose section of the document says (this week):
After successful authentication, the caller is routed to the main menu, where they can select from Checking (1), Savings (2), or Investments (3). At any time during the menu, the caller can press 0 to be transferred to a live agent.
What the accompanying Visio diagram shows (also this week, copied from last week):

Three discrepancies in one screen: Investments in the prose is Retirement Accounts in the diagram. The prose mentions a press-0 agent option that the diagram doesn’t show. The diagram has a press-9 fraud option that the prose doesn’t mention. Which is authoritative? Nobody knows. The developer picks one. The QA tester picks the other.
Integration assumptions: the wrong artifact for the job
The first three failure modes are cases where the design document could have specified more and didn’t. Integration is different. It’s a case where the design document fundamentally cannot carry the load, no matter how much detail you add.
REST API contracts, authentication flows, timeout and retry behavior, payload structure, error response mapping, idempotency. None of this belongs in a document whose primary audience includes business stakeholders. Trying to put it there either bloats the document past usability or strips out the detail that developers actually need.
This is where the single-document model isn’t just inefficient. It’s structurally incapable. You need a separate software design document (or equivalent technical specification) that the high-level design references but does not try to contain. The high-level design needs enough about integrations to make the call flow coherent (“at this step, the system retrieves the caller’s account; if it can’t, route to agent”), and no more.
The honest version of the argument: you cannot do this in a single document, no matter what. Some content belongs in a different artifact entirely, and pretending otherwise is the original sin of the standard approach.
What the design document says:
The system retrieves the caller’s account information from the core banking system.
What the developer needs to actually implement that line:
| Concern | Required Detail |
|---|---|
| Endpoint | GET /api/v2/accounts/{accountId} on core-banking.internal |
| Authentication | OAuth2 client credentials; token endpoint, scope, refresh policy |
| Request shape | Path param accountId; required headers (X-Correlation-Id, X-Caller-Channel: IVR) |
| Response schema | Fields the IVR consumes, types, nullability, units |
| Timeout policy | Connect timeout, read timeout, total budget before the IVR must give up |
| Retry policy | Which HTTP status codes are retryable, max attempts, backoff |
| Error mapping | 404 → “account not found” flow; 5xx → agent transfer; 401 → re-auth |
| Fallback | What the caller hears if the system is unreachable; whether to offer callback |
| Idempotency | Whether retries are safe; idempotency key handling if not |
None of this belongs in a document the business is signing off on. All of it has to exist somewhere. The “somewhere” cannot be the same artifact.
No testable acceptance criteria
QA inherits whatever the design document gives them. When the document is structured as narrative prose with embedded business rules, what QA gets is a description of intent, not an enumeration of states, transitions and expected behaviors that can be tested independently.
The practical consequences:
- Test cases are reverse-engineered from prose, which means QA is interpreting the same ambiguous text developers already interpreted, often arriving at different interpretations
- Features cannot be tested in isolation because the document doesn’t decompose the system into independent units; there’s no clean answer to “what does it mean for the account-lookup step to be working correctly?” separate from the end-to-end flow it sits inside
- Coverage is built from guesswork, especially on exception paths: if the document doesn’t enumerate the error branches, QA doesn’t know what to test
- Defects found in UAT are re-litigated as design questions, because there’s no authoritative spec to point to. “Is this a bug, or is this what was designed?” becomes a meeting instead of a lookup
This closes the loop on the opening pain. QA can’t test features in isolation isn’t a QA problem. It’s a direct consequence of a document that was never structured to support feature-level testing in the first place. Acceptance criteria aren’t missing because someone forgot to write them; they’re missing because the artifact format doesn’t have a place for them.
What the design document says:
The caller is prompted for their account number. If the input is invalid, the system re-prompts. After multiple failed attempts, the caller is transferred to an agent.
Test cases QA can write from this:
- Verify that an invalid account number causes a re-prompt
- Verify that repeated failures eventually transfer to an agent
That’s about all you can write without inventing details. End-to-end, vague and impossible to automate cleanly.
Test cases QA could write from proper requirements:
- The IVR MUST accept account numbers matching
^[0-9]{5}-[A-Z0-9]{3}$. - On no-input, the IVR MUST replay the prompt within 500ms of the 5-second timeout.
- The IVR MUST allow exactly 2 no-input retries before transferring to an agent.
- The IVR MUST allow exactly 2 no-match retries before transferring to an agent.
- No-input retries and no-match retries MUST be counted separately.
- On the second no-match, the IVR MUST play the help prompt before re-prompting.
- On max-retry, the IVR MUST log the failure reason (
no_inputvsno_match) before transferring.
Seven independently verifiable test cases instead of two end-to-end gestures. Same node. Different artifact shape.
The Compounding Cost
Each failure mode above looks like a small gap in isolation. Together, they produce the IVRs everyone complains about.
The costs compound in predictable ways. Ambiguities in the document become assumptions in the code. Assumptions in the code become defects in QA. Defects in QA that can’t be traced to a definitive spec become design questions, which become meetings, which become change requests, which slip the schedule. The IVR ships anyway (it has to), and the gaps that didn’t get caught become production incidents, escalations and a backlog of “small fixes” that never empties.
There is a well-known cost curve in software: defects discovered late cost an order of magnitude more to fix than defects discovered early. IVR projects sit at the painful end of that curve by default, because the artifact that should catch defects in design can’t catch them. It doesn’t contain the precision needed to expose them.
The hidden tax is even larger than the visible one. Every IVR project re-litigates the same ambiguities, because the document format guarantees they will exist. The same hallway conversations happen on every project, in every organization, with every vendor. The institutional learning curve is flat, not because the people are bad but because the artifact doesn’t accumulate.
Where We’re Going
The design document isn’t a problem with IVR design. It’s the problem, because it institutionalizes ambiguity at exactly the handoff where precision matters most. The symptoms in the opening (missing requirements, slipped dates, untestable builds) are not separate failures to be addressed individually. They are what the guide-vs-spec gap produces, and they will keep producing themselves as long as the gap exists.
Part 2 will argue that the fix isn’t a better document. It’s inverting which audience the source artifact is built for. The industry is moving rapidly toward agentic workflows that require machine-readable, structured source artifacts, and IVR design, with its decades of Word-and-Visio inertia, is overdue to catch up. The path forward is to build the specification machine-native first, and generate human-facing views from it: the polished PDF for the business, the rendered diagram for review, the developer spec, the test plan. All from one source of truth, all consistent by construction, all consumable by both humans and the agents that will increasingly do the implementation work alongside them.
That’s Part 2.
