Build a keypad phone menu with the Voice API

Collect DTMF with IllyVoIP, route Sales and Support selections, and handle no input, invalid choices and transfer outcomes in your application.

Build a programmable “Press 1 for Sales, 2 for Support” menu by gathering DTMF on an active IllyVoIP call, validating the returned digits and starting a tracked transfer. Your application owns the branching logic; the gather request does not automatically create a complete menu.

This guide uses backend API calls. If you want to build a menu visually, use the existing Voice Agent Studio phone-menu guide instead. You need API access, a live call-control session, two destinations permitted for your account and a recovery path when neither destination answers.

Validate before transferring. Explanatory diagram showing the workflow, not an interface or completed transaction.
Validate before transferring. Explanatory diagram showing the workflow, not an interface or completed transaction. Open the image for a larger view.

1. Obtain the active call-control ID

For an incoming call, configure the number’s supported Voice API Control destination, receive and verify the incoming event, and answer while the call is eligible for API answering. Save the returned call_control_id. A public incoming call_id is not interchangeable with the control-session ID used here.

For an outbound session, create the call once and wait until status establishes it is answered and active. Do not start gathering while the destination is only ringing. All requests below use X-Api-Key authentication and JSON bodies.

2. Define a small allowlist of destinations

Store Sales and Support destinations on the server. Map exactly 1 and 2 to those choices. Never treat arbitrary keypad input as a phone number to dial. Keep the target type and value together: pstn uses an external destination, while sip_user uses an authorized SIP identity belonging to the account.

Set a maximum number of completed menu attempts, such as two, and decide what to do after silence or repeated invalid input. Your fallback might explain that no selection was received and end the call. It must not loop indefinitely or assume an operator is always available.

3. Request a DTMF selection

POST to https://api.illyvoip.com/api/v1/voice/call-control/gather with a saved Idempotency-Key for this prompt-and-input attempt:

{
  "call_control_id": "YOUR_ACTIVE_CALL_CONTROL_ID",
  "input": "dtmf",
  "prompt_text": "For Sales, press 1 followed by the hash key. For Support, press 2 followed by the hash key.",
  "timeout_ms": 8000
}

The prompt deliberately asks for the hash key to finish input. The documented request does not require an invented one-digit option. Without a terminator, collection can wait for its timeout. Compare the complete returned value; do not accept the first character of a longer invalid entry.

Prompt text incurs TTS usage. If you choose a voice, obtain its current identifier from the TTS catalog using context=voice_api; otherwise the default applies. This DTMF example does not request speech recognition. Keep the prompt short and allow enough response time for your callers.

4. Branch only after a completed result

On HTTP and API success, verify input is dtmf and read digits. Do not read transcript as though this were speech recognition. Use this explicit decision table:

ResultApplication action
Exactly 1Choose the saved Sales target.
Exactly 2Choose the saved Support target.
Empty digitsNo selection; offer the bounded retry or end politely.
Any other valueInvalid choice; do not dial the input.
Pending or errorReconcile the action; do not count it as a completed menu attempt.

For a new completed retry, save a new action key and the new prompt. While voice_request_pending remains unresolved, keep the existing key. A content-review refusal is not an empty keypad selection, and approval does not replay a refused request.

5. Start and follow the selected transfer

POST this shape to /api/v1/voice/call-control/transfer after replacing the saved target. The example uses an account SIP identity, not an arbitrary SIP URI:

{
  "call_control_id": "YOUR_ACTIVE_CALL_CONTROL_ID",
  "target": {"type": "sip_user", "value": "YOUR_SALES_SIP_IDENTITY"}
}

Save transfer_id and follow /api/v1/voice/call-control/transfer-status. A connecting or ringing result is not a completed connection. If the transfer fails or is canceled, inspect terminal, resume_original and the current call status before playing a fallback. Do not issue new media actions merely because a polling deadline elapsed.

6. Check the complete caller experience

Test fixtures for 1, 2, silence, multiple digits, invalid input, a caller hanging up during the menu, a pending gather and a transfer that never connects. Verify only allowlisted targets can be selected and no branch loops forever. Then use authorized live calls to verify keypad detection, prompt timing, target ringing and the fallback audio.

Keep the control ID, gather attempt reference, returned digits and transfer ID with safe timestamps for investigation. Avoid logging keys or unnecessary caller information. If digits are not detected, compare the sending system’s DTMF configuration and observed call evidence before changing global PBX settings.

Frequently asked questions

Does this create a Voice Agent Studio flow?

No. This is application code controlling the API. The visual flow builder is a separate authoring workflow.

Can I collect a long account number with this menu?

This guide validates two fixed choices. A longer input requires its own length, timeout, privacy and validation design against the current API contract.

Related guides and official references

Reviewed on 8 October 2026 against the current IllyVoIP implementation and available official documentation. Examples use placeholders and are not evidence of a completed live transaction. Check your account’s current availability and prices before executing paid requests.