sphoroVOICEdocs
Markdown

Keypad and IVR

"Press 1 to confirm." A keypad menu answers in milliseconds because nothing in the turn is a network call — no recognizer, no model, no voice synthesis. It is also the part of a call that keeps working on the day a speech vendor is having an outage.

Two ways to use it

An agent in keypad modeKeypad on a conversation agent
What it isA menu and nothing elseShortcuts alongside a real conversation
SpeechNever listened toListened to as normal
Costs per turnNothingNothing, for the keyed turns
Reach for it whenThe whole job is a fixed menuYou want "press 9 for a person" to work at any point

The keypad map is read in both cases. The mode decides whether anything else is.

Building a menu

PATCH/v1/agents/{id}
Shell
curl -s -X PATCH https://voice.sphoro.com/v1/agents/$AGENT_ID \
  -H "Authorization: Bearer $SPHORO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "keypad",
    "languages": [{
      "code": "en-IN",
      "greeting": "Thanks for calling Acme. Press 1 to confirm your appointment, 2 to cancel it, or 9 to speak to somebody."
    }],
    "keypad": {
      "1": { "say": "Your appointment is confirmed. We will see you then. Goodbye.", "end_call": true },
      "2": { "say": "Your appointment has been cancelled. Goodbye.", "end_call": true },
      "9": { "say": "Putting you through now." }
    },
    "transfer_number": "+912261234567",
    "no_match": "Sorry, that is not one of the options.",
    "no_input": "Press 1 to confirm, 2 to cancel, or 9 to speak to somebody.",
    "max_no_input": 2,
    "keypad_timeout_seconds": 6
  }'
The greeting is the menu. There is nothing else to read the options out, so an agent in keypad mode with a one-line greeting has a menu nobody can use.

The entries

FieldTypeDescription
keypad
optional
objectDigit to action: {"1": {"say": "…", "end_call": true}}. Keys are single characters — 09, * and #. Replaces the whole map when sent, so read-modify-write to add one entry.
say
optional
stringSpoken back when that key is pressed. Supports {{variables}}.
end_call
optional
booleanHangs up once say has been spoken in full — not the instant the line ends, so the caller hears all of it.
transfer
optional
booleanPuts the caller through to a person once say has played — "press 1 for sales". Goes to transfer_to, or to the agent's transfer_number when that is empty. Needs a carrier that can redirect a live call; where the call cannot be put through it ends as transfer_unavailable or transfer_failed rather than leaving the caller in the menu.
transfer_to
optional
stringThis key's own destination, E.164. Lets 1 and 2 reach different teams.
no_match
optional
stringSpoken when the caller presses something with no entry. Three in a row ends the call.
no_input
optional
stringSpoken when nothing is pressed — usually the menu again.
max_no_input
optional
integerHow many times to re-read the menu to a silent caller before giving up. The call ends no_input.
keypad_timeout_seconds
optional
integerHow long to wait for a press before treating it as silence.

Handing a menu over to a conversation

An entry with no end_call simply speaks and waits. On a conversation agent that is where the model takes over — which is how "press 3 for billing" leads into an actual billing conversation rather than a dead end.

JSON
{
  "mode": "conversation",
  "keypad": {
    "9": { "say": "Putting you through now.", "transfer": true },
    "3": { "say": "Sure — tell me what the billing question is." }
  }
}

Pressing 9 hands the call to a person — at the agent's transfer_number, or at the entry's own transfer_to. See transfers and endings.

Digits arrive two different ways

This is the part that makes keypad handling look flaky when it is not, and it is worth understanding before debugging anything.

WayWhat happens
Signalled by the carrierThe carrier tells us a key was pressed, out of band. Reliable, and the usual case on a real telephone.
Heard in the audioThe tone is in the media stream and is detected there. This is what a browser call produces — a web dialpad generates real tones into the audio, because there is no carrier to signal on its behalf.

Both end up in the same place. Every press writes a call.dtmf line to the call's events saying which key it was and what became of it — matched an entry, matched nothing, arrived too late. When a menu "sometimes works", that line is the answer: it will show which of the two paths that call took.

GET/v1/calls/{id}/events
Shell
curl -sN https://voice.sphoro.com/v1/calls/$CALL_ID/events \
  -H "Authorization: Bearer $SPHORO_API_KEY" | grep dtmf

On Vobiz

Vobiz does neither: its media stream carries no key events, and mobile networks send the key out of band, so no tone reaches the audio. A keypad agent on Vobiz therefore does not stream. Vobiz runs the menu itself — it plays each line in the agent's voice, collects the key, and asks this platform what the key does. Keys, lines, transfers and endings behave as they do anywhere else and are written to the transcript the same way. Two things differ: the call is not recorded, and a key pressed while a line is playing is taken when the line finishes. A conversation agent's keypad entries cannot be pressed on Vobiz; use Plivo or Twilio for those, or let callers say the option.

Keying ahead

A keypress always interrupts, whatever interrupt_words says, and it interrupts the greeting too. That matters more than it sounds: every regular caller keys ahead of a menu they already know, and an agent that finishes reading all six options first has made them wait for nothing.

Keep menus to four options and one level. Callers do not hold six options in their head, and a second level of menu is where people start pressing 0 to escape. If the tree is genuinely deep, the honest answer is a conversation agent that asks "what can I help with?" — which is the thing a deep menu was approximating.

What a keypad agent does not have

Set on a keypad agent, these are stored, returned, and read by nothing — so switching back to conversation restores the agent exactly as it was:

  • The prompt, the model, temperature and max_tokens.
  • knowledge_base_ids and tools.
  • The conversation block's silence and turn-taking settings — a keypad agent uses keypad_timeout_seconds and max_no_input instead.

What it does read: languages (for the greeting and the voice), keypad, the four keypad fields, transfer_number and record_calls.