Step URLs
A step with a URL (Lookup → URL in the step editor) calls your server while the caller is on the line. Use it to:
- be told what the caller chose, the moment they choose it;
- decide what the caller hears — a balance, an order status, a name;
- route the call — to a step, a queue, an agent — on your own logic;
- save a value from your system onto the call record.
This is the opposite direction from Calling into a Flow, where your code calls Telebroad.
When the request is sent
The URL is called when the call enters the step, before the step's audio plays — so its reply can change what is said. It is called every time the step runs (a step revisited by a "repeat" menu calls it again), but not again for a step's re-ask or confirmation prompts.
call enters step ──▶ GET/POST your URL ──▶ reply ──▶ step plays text / collects keys / routes
(≤ 8 s)
The request
| Method | GET, or POST when the step's Request Profile uses POST. With None (no profile picked) the step uses the flow's profile named default if there is one, otherwise a plain GET with no authentication. |
| URL | Your URL with its {placeholders} filled in. Values are inserted as-is, not URL-encoded — see Encoding. |
| Query string | Whatever your URL contains, plus each referenced call value, earlier answer or saved value appended under its own name (URL-encoded). A value reaches you only if you reference it. |
| Headers | The profile's Authentication, if any: Authorization: Bearer <token>; your Header Name (default X-API-Key) with your API Key Value; or Basic auth. Content-Type when there is a body. No signature header. |
| Body | None, unless the step sets Body (POST only): Form — one key={placeholder} per line, sent as application/x-www-form-urlencoded; or JSON — your JSON document with placeholders filled JSON-escaped, sent as application/json. |
| Timeout | 8 seconds for the whole request. |
| Redirects | Followed, if each hop passes the address check below. A request with a body is never redirected — a redirect fails the step. |
| Retries | None. A failed request is not repeated. |
The URL must be http or https and must resolve to a public address; private,
loopback and internal addresses are refused when you save and again on every call.
For example, with a GET profile and this URL:
https://example.com/hooks/appointment?id={appointment_id}&answer=confirmed
your server receives:
GET /hooks/appointment?id=A-20931&answer=confirmed HTTP/1.1
Host: example.com
Authorization: Bearer 7f3c…
And with a POST profile and a JSON Body of
{"appointment": "{appointment_id}", "choice": "{step_1}"}:
POST /hooks/appointment HTTP/1.1
Host: example.com
Content-Type: application/json
Authorization: Bearer 7f3c…
{"appointment": "A-20931", "choice": "1"}
Correlating with the API request: {params_id}
On a call placed with POST /flows/{flowId}/calls, the
placeholder {params_id} in a step URL or body is the data.call.id that request
returned. Use it to tie the callback to your request if you did not send your own
id in params. On inbound calls it has no value (and is sent as the literal
{params_id} in a URL).
The reply
Answer with any 2xx status. The body is read as a JSON object; every
field is optional. Up to 1 MB is read.
{}, an empty body, or a body that is not a JSON object (plain text, HTML, an array): the step carries on exactly as configured. Nothing in the body is spoken. Use this when you only want to be told.- Any other status (
3xxthat cannot be followed,4xx,5xx), a timeout, or no connection: the step fails — see When your server fails.
Reply fields
What the caller hears
| Field | Type | Effect |
|---|---|---|
text | string | Replaces the step's spoken text for this call. {placeholders} in it are filled. Plays instead of the step's saved sound. |
params | object of strings | Fills {placeholders} in this step's spoken text (the step's own, or your text). Only this step. If any value is not a string, the whole object is ignored. |
voice | string | Voice for this step, e.g. en-US-AvaMultilingualNeural — a name from the editor's voice picker. |
mp3 | string | URL of an MP3 to play, before any text. Must be a public http/https address; a refused address is skipped. |
How input is collected
| Field | Type | Effect |
|---|---|---|
input | object | Replaces the step's Capture input settings for this call, and turns capture on if the step had none (it needs from/to or maxLen). Same shape as the step's settings: from/to (a value range) or minLen/maxLen (a length), timeout (default 5), retries, confirm: {enabled, text}, saveAs, onNoInput / onInvalid: {prompt, action, target}. A target is a step id or "type:number". Value labels come only from the editor. It cannot switch off Sensitive on a step that has it. If the object does not parse, it is ignored. |
Where the call goes
| Field | Type | Effect |
|---|---|---|
nextStep | string | The step (by internal id, e.g. step_3) to run next. Beats keypress routing. |
digitRoutes | object | Keypress → target for this call: {"1": "step_3", "2": {"step": "step_4"}, "0": {"dtype": "hangup"}}. Matched against the keys pressed on this step. |
dtype, dnumber | string | The destination the call goes to if the flow ends after this step (replaces Default routing's Final/Sys). |
failureDtype, failureDnumber | string | Failure destination if this step's routing fails (e.g. a nextStep that does not exist). A later step's own On error replaces it. |
nextUrl, nextUrlConfig | string | After this pass (its audio and input) finishes, run the same step again, calling nextUrl (with the Request Profile named in nextUrlConfig) instead of the step's URL. Its reply decides the second pass. |
Prefer nextStep to dtype/dnumber: build the destinations as steps in the
editor (Default routing → Final/Sys) and return the step's id. Destination
type names are internal; a type that needs a number but is sent without one
(including an unknown type sent without a number) cannot be routed and the call hangs up, with "Unroutable
destination" in Call Logs.
A nextStep or digitRoutes target that is not a step in the flow ends the flow
on the step's failure destination.
Example:
{
"text": "Hi {first_name}, your balance is {balance}. Press 1 to pay now, or 2 to speak to billing.",
"params": { "first_name": "Dana", "balance": "$142.50" },
"input": { "minLen": 1, "maxLen": 1, "timeout": 6, "retries": 1 },
"digitRoutes": { "1": "step_4", "2": "step_5" }
}
Reading fields from the reply in the editor
Two step settings read your reply without any of the fields above, so the reply can be your system's natural JSON:
- Route by value (optional) — name a field (
status, or a dotted path such asdata.status). Its value is matched against the step's Named routes (Event names) and the call goes to that rule's target. A string, number or boolean value works. A missing field, a non-JSON reply, or a value with no matching rule fails the step. - Save a value to Call History — Response value (a field or dotted path)
and Save as (a name). The value is saved on the call as
Flow Data and is available to later steps as a
placeholder (
Save as"Payment ref" →{payment_ref}). Each value is kept to 256 bytes during the call and stored to 64 characters. Field and name may not look like card data or a credential — anything containingcard,cvv,token,passwordorsecretis refused.
{ "data": { "status": "approved", "ref": "PAY-77812" } }
With Route by value data.status and Named routes approved → a "Thanks"
step and declined → a "Try again" step, plus Save data.ref as "Payment
ref", this reply sends the call to the Thanks step and saves PAY-77812 on the call.
When your server fails
The step fails when its URL:
- does not answer within 8 seconds;
- cannot be reached, or is refused by the address check;
- answers with a non-
2xxstatus; - (with Route by value) returns no usable value, or one no rule matches;
- (with a Body) would be redirected.
A failed step does not play its audio. It takes its On error route — which hangs up by default. (Every step saved in the editor has an On error; a step with none would play its audio and carry on.) If a failure on your side should not cut the caller off, point On error at a step that still helps them ("Sorry, we can't reach our system right now — transferring you…").
The failure, the status and your reply body are recorded in the flow's Call Logs (Webhook Response, Error).
:::caution Keep the whole step fast Your 8 seconds are part of a budget of about 10 seconds for the whole step, which also includes producing its speech. Long text with a slow voice, plus a slow reply, can push a step past it — the step then fails and takes On error. Aim to answer in well under a second and keep spoken text short. :::
Security
- There is no signature on step URL requests. Authenticate them with a
Request Profile — Bearer token, an API key header, or Basic auth — and keep
the credential in a Secret variable
(
{api_token}as the token). Do not put credentials in the URL: the URL is visible to everyone who can edit the flow, and ends up in access logs. - Use
https. Required for a body that carries a Sensitive answer or a secret; advisable for everything. - Treat inputs as untrusted.
{callerid}can be spoofed by the caller's carrier, and keypresses are whatever the caller typed. Validate them before you act on them, and never authenticate a person by caller ID alone. - Make the endpoint idempotent. The same step can run more than once in one
call (a "repeat" menu,
nextUrl), and you cannot always tell a slow success from a failure. Key your updates on your own id fromparams(or{params_id}), not on "first request wins". - Answer fast, work later. Reply as soon as you have recorded the event; do slow work (emails, CRM updates) after responding.
- Card data. Collect a card number only on a Sensitive step, and send it only in a POST Body to your gateway — the only place the flow lets it go. It never appears in the flow's Call Logs, Call History or a URL. A reply to such a request is not stored in Call Logs; only its status and the fields you route on or save.