Collect caller input
Ask the caller a question, collect what they say or press, and send the call down a branch based on the answer. That one pattern is a department menu, a yes-or-no confirmation, or a request for an account number, and it works the same whether the call reached your flow from a phone number, a SIP address, or a browser. Start by asking one question and branching on the reply, then handle silence and unmatched replies, track what the flow heard, and extend the prompt to keypad digits and a retry.
Pick the right product for collecting caller input
- SWML is a JSON or YAML document that SignalWire runs step by step. Generate it with the
Server SDKs or write it by hand, then host it in your Space or serve it from your
own server. Its
promptmethod asks and collects in one step. To compare the reply on your own server instead,transferorexecuteto a URL: SignalWire posts the document’s variables,prompt_valueamong them, and runs the SWML you return. - Relay is the WebSocket client in the Server SDKs. Your process receives the call, plays the question, and gets the reply back as a value it can compare.
- The REST Calling API sends the same collect command over HTTP to a call you already hold the ID for. The reply arrives as a webhook.
- Call Flow Builder is a visual editor in the Dashboard. Its Gather Input node asks, collects, and branches without code.
The Browser SDK places and receives calls from a web page. It is the caller or the callee, never the flow, so it has no column here.
Prepare to collect caller input
Start from working inbound call setup in Make and receive calls: a SWML Script, Relay Application, or Call Flow that a call can reach. This guide picks up once a call reaches your flow. How an incoming call reaches your code explains why the same flow answers a phone number, a SIP address, a browser call, or the Dashboard’s Click-to-Test button. To pick a voice for the questions, see Speak text on a call.
Have these values ready:
- Your Space URL, such as
<YOUR_SPACE>.signalwire.com. - For the Relay and REST paths, your Project ID and an API token from the Dashboard’s API credentials page, with the Voice permission enabled. A hosted SWML script or a Call Flow needs no credentials.
- Python 3 or Node.js for the served SWML and Relay samples. A served document also needs the tunnel and basic auth password set up in Answer a call via SWML.
Replace these placeholders in the samples. The hosted SWML and Call Flow Builder paths use none of them.
Ask a question and branch on the answer
In every surface the pattern is the same: play a question, wait for input, compare the result, and continue down one path. Bayview Taxi asks callers to say “booking” or “dispatch”. Speech hints tell the recognizer which words to expect, which makes short single-word replies reliable.
Ask a question and branch via SWML
Three methods do the work. prompt plays the question and waits. Setting any
speech parameter, such as speech_hints, switches it from keypad digits to speech; the recognized
text lands in the prompt_value variable and the outcome in prompt_result.
switch compares a variable against case keys and runs the matching list, or
default when nothing matches. transfer jumps to another section and does not
return.
The booking and dispatch sections each speak a different line, and no_match catches
everything else. The Server SDKs build the document with the SWML builder and serve
it over HTTP; the YAML and JSON forms are what you paste into a hosted script. The builder’s
methods append to main; the named sections go through the service.
switch compares the whole value exactly. SignalWire lowercases recognized speech and strips its
punctuation before storing it in prompt_value, so “Booking.” still matches the booking case,
but “booking please” does not.
Run the program and point an External URL SWML Script at it through your tunnel, or paste the YAML
or JSON into a Hosted Script, as in Create the SWML Script. To put dispatch
callers through to a person instead of playing a line, the dispatch section can
connect the call to a phone.
Then open the Resource under My Resources and select Click-to-Test, under the Resource’s name. A new tab opens, shows “Connecting”, and places a browser call to the Resource. Allow microphone access if the browser asks. Call three times: say “booking”, say “dispatch”, and say nothing. You hear a different line each time.
If you don’t, match the symptom:
- The call connects but stays silent. The flow never ran. Answer a call via SWML shows how to confirm that SignalWire can fetch a served document and that the Resource points at it.
- Every call ends in the
no_matchline. The recognized text did not equal a case key. Track what the flow heard shows the text SignalWire stored. - Click-to-Test is disabled. It appears only on Resources that handle calls and have an address. Open the Resource’s Addresses & Phone Numbers tab and add an alias.
Ask a question and branch via WebSocket (Relay)
The Server SDKs expose the prompt as play_and_collect on the call. It plays the
question and resolves when input arrives, times out, or fails. The result carries a type of
speech, digit, no_input, no_match, or error, and for speech the recognized text with a
confidence score. Your code compares the text and continues.
Because the comparison runs in your code, you can match loosely: "booking" in heard accepts
“booking please” as well as “booking”. To put dispatch callers through to a person,
connect the call to a phone instead of playing the reply.
Run the client and create a Relay Application Resource whose Topic is default, the value in
the client’s contexts, as in Answer a call via WebSocket (Relay). Then open the
Resource under My Resources, select Click-to-Test, and call three times to hear the three
outcomes. If the client logs an authentication error at startup, the project, token, and Space do
not belong together. If the call connects but stays silent, check that the client process is still
running and that the Resource’s Topic matches its contexts.
Over REST, the same collect runs as the calling.collect command on a call you already hold the
ID for, such as one a hosted script answered. REST can’t play the question and collect in one
command, so speak the question with calling.play first, and send the collect once
playback finishes with start_input_timers set to true. Without it, the wait for input doesn’t
start until you send calling.collect.start_input_timers. The reply arrives at status_url as a
calling.call.collect event instead of a return value, and control_id names the collect so you
can match the event to it.
The Calling API reference documents every calling.collect field, and the
collect status callback reference the event that reaches status_url.
Ask a question and branch via Call Flow Builder
Open Tools > Call Flow Builder in the Dashboard, select Add New, name the flow, and select Save. Open the flow’s More Options menu and select Edit to reach the canvas. Every flow starts from the Handle Call node. Connect these nodes in order:
- Answer Call.
- Gather Input, with “Thanks for calling Bayview Taxi. Say booking or dispatch.”
as its Text to Speech. Every input option pairs a key with a word: add one option with
Caller presses
1and Or saysbooking, and a second with2anddispatch. The node grows one output connector per option, plus Unknown and No Input. - A Play Audio or TTS node on each option’s connector, with the matching line, and a Hangup Call node after each. To put dispatch callers through to a person, use a Forward to Phone node instead.
Leave Unknown and No Input for the next task. Select Deploy to make this version the live one; the versioning guide explains how deployed and draft versions relate. A deployed Call Flow is a Resource under My Resources, so Click-to-Test works here too: call three times and hear the three outcomes.
Handle silence and unmatched speech
A caller who says nothing, or whose words the recognizer could not make out, must still get an answer. Each surface reports the two cases separately, so the flow can say something different for each. A reply the recognizer did understand comes back as text whether or not it is one of your words, so an off-menu reply is caught by your comparison, not by these cases.
Handle silence and unmatched speech via SWML
After prompt, prompt_result is match_speech, no_input, no_match, or failed, and
match_digits once keypad input is enabled. match_speech means the recognizer returned text, not
that the text is one of your cases. The default case already routes everything but a
match to no_match; this change makes that section say which one happened, using a JavaScript
expression inside the spoken text. Only the no_match section changes.
The wait for input before no_input is five seconds by default; prompt.initial_timeout changes
it.
Handle silence and unmatched speech via WebSocket (Relay)
The collect result’s type is no_input when the caller stayed silent. Anything the recognizer
transcribes comes back as speech, whether or not it’s one of your words, so your comparison
catches an off-menu reply. no_match means the recognizer returned no text at all. Replace the
reply selection in the Relay sample:
Handle silence and unmatched speech via Call Flow Builder
Connect the Gather Input node’s No Input connector to a Play Audio or TTS node that says “I didn’t hear anything. Goodbye.”, and its Unknown connector to one that says “I didn’t understand that. Goodbye.” Connect both to a Hangup Call node and select Deploy.
Track what the flow heard
Three places show what happened on the call: the Dashboard’s call log, a webhook you host, and the Relay client’s own event stream.
See the flow’s steps in the Dashboard
Open Logs > Voice in the Dashboard and select the most recent call. The call’s timeline lists each step the flow ran in order: the call state changes, a Play entry for each spoken line, and a Collect entry for the prompt. Select an entry to see its details. A document that fails to load or contains an invalid method shows a Script Warning or Error entry with the reason, so a broken flow is visible here without a support ticket.
The timeline records that input was collected, not what the caller said. For the recognized text itself, use one of the two paths below.
Receive the result via SWML
Add a status_url to the prompt method. SignalWire sends a calling.call.collect event to that
URL when input arrives, and calling.call.play events to the same URL as the question plays, so
check event_type before reading params.result. The same collect event reaches the status_url
on a REST calling.collect command.
The POST body for a recognized reply looks like this. params.result.type is no_input or
no_match for the failure cases, with no params.result.params.
The collect status callback reference documents every field of the event, and
the prompt reference the play events that reach the same URL.
Receive the result via WebSocket (Relay)
The Relay client already holds the result: it is the event that action.wait() returned. To log
every collect event as it happens, register a handler on the call before starting the prompt. The
Server SDKs document the fields of every call event.
Compare the surfaces
The Dashboard’s call log shows that a Collect step ran, for every surface, but not its outcome and never the text.
Examples
Accept a keypad digit as well as speech
Some callers are in a noisy place or on a handset that makes speaking awkward. Offer keys and words at once, and treat “press 1” and “say booking” as the same answer.
Accept a keypad digit as well as speech via SWML
Setting one digit parameter alongside the speech parameters enables both inputs. max_digits
joins the prompt, and the digit keys join the switch.
Accept a keypad digit as well as speech via WebSocket (Relay)
Pass both digits and speech in the collect config. A keypad reply comes back with type
digit and the pressed key in params.digits, so the code maps keys to the same words.
Accept a keypad digit as well as speech via Call Flow Builder
The flow you built already accepts both, because every Gather Input option carries a Caller presses key as well as an Or says word. Change the prompt text to mention the keys and select Deploy.
Ask again when the caller says nothing
One retry turns a dead end into a second chance. Ask the question again after silence, then give up.
Ask again when the caller says nothing via SWML
goto jumps to a label within the same section, and its when condition and max
count keep the loop bounded. A label before the prompt and a conditional goto after it repeat
the question once. The control flow guide compares goto with execute and
transfer.
Ask again when the caller says nothing via WebSocket (Relay)
A loop around the prompt exits on the first reply of any kind and gives up after two silent attempts.
Ask again when the caller says nothing via Call Flow Builder
A counter bounds the loop. Route the Gather Input node’s No Input connector to a
Set Variables node that sets count to %{count} + 1, then to a
Conditions node with the condition %{count} > 1. Connect the condition’s
connector to a Hangup Call node, and Else to a Play Audio or TTS node that says “I
didn’t hear anything.” and leads back to Gather Input. The Gather Input reference builds the
same flow in Input with infinite loop prevention.