Steps
Every kind of step in an outreach workflow — Start, Call, Read call results, Check call history, HTTP request, Wait for your system, WhatsApp, SMS, email, Run an automation, Wait, Try again and End — what each does, its settings, and what it gives the steps after it.
Every workflow has one Start and at least one End. Between them, each
step does one thing and then takes a path to the next. In the dashboard you pick
a step from the palette and fill in its panel. Through the
API, a step is one entry in the definition's
nodes, with a type and a config.
| Step | type | Does | Gives the next steps |
|---|---|---|---|
| Start | start | Where a contact enters | The contact, as entry.* |
| Call | agent | Calls the contact with one of your agents, by phone or in your app | How the call went, as call.* |
| Read call results | extraction | Reads what the agent's post-call analysis found in that call | The fields you picked, as extraction.* |
| Check call history | history | Counts the calls already placed to the person lately, by any of your agents | How often they were called and answered, as history.* |
| HTTP request | api | Sends one HTTP request to your system | Its answer, as response.* |
| Wait for your system | callback | Waits until your system calls back, or a timeout | What your system sent, as callback.* |
| Send a WhatsApp message | whatsapp | Sends an approved template, or a text, on your connected WhatsApp number | How it went, as message.* |
| Send an SMS | sms | Sends a text message with your SMS connection | How it went, as message.* |
| Send an email | email | Sends an email with your email connection | How it went, as message.* |
| Run an automation | automation | Starts one of your automations with the run's values | Whether it started, as automation.* |
| Wait | time | Waits for a while, or until a time | — |
| Try again | retry | Goes back to an earlier step later, a limited number of times | — |
| End | end | Ends the run with a label and an outcome | — |
Start
The fields every contact brings. mobile_number, name, email,
reference_id, user_id (the person's ID in your app) and user_field (which
of your app's fields that ID is in: user_id, email, phone or your own) are
always accepted; add your own, each with a type and whether it is required: string, number, boolean, timestamp, phone, email,
url or json. A value is converted to its type as the contact enters
("50,000" becomes 50000). A contact missing a required field is refused
there and then, before anything is called.
reference_id is how a contact is told apart: a contact can only have one run
of a workflow under way at a time. Leave it out and the phone number is used, or
the user ID for someone with no number.
Call
Calls the contact with one of your agents, then waits for the call to end. Under How to reach them, choose a Phone call (the default) or In their app: the person's phone rings in your app, as a call does (In their app below).
| Setting | Meaning |
|---|---|
Agent (agent_id) | Any of your agents, prompt or flow. |
How to reach them (channel) | phone (the default) or app. |
Number to call (recipient) | A phone call: a value to call, entry.mobile_number unless you choose another. |
Caller ID (from_number) | A phone call: the number the person sees, picked from your Phone numbers and stored exactly as that page lists it. Leave it to us and one of your numbers is chosen, as for any call you place through the API. |
Reason (reason) | Optional, up to 120 characters: why the agent calls. The agent hears it as call_reason; values go in as {placeholders}. |
Wait for the call (timeout_s) | How long to wait for the call to end once it is dialled (in the app: once it rings): 1 minute to 1 day, 1 hour by default. |
Values for the agent (variables) | Extra values for the agent's prompt. The contact's fields are always sent, except the user ID unless you add it here. Your agent uses lists the {variables} in the agent's prompt and greeting (a flow agent's values that come with the call), and says which the run sends; Add … to the contact makes the missing ones contact fields, which a test run, a list and your system then fill in. |
How the call went is in call.status: completed, no_answer, busy,
voicemail, failed, cancelled, timeout (it did not end in time) or
error (it could not be placed: no number, or the agent was deleted). With it
come call.duration_s and call.execution_id, the call's id in Call History.
A call held back is waited for, not failed
When your balance is empty, the agent is switched off, it is outside the
agent's calling hours or every line is busy, the call waits and goes out as
soon as it can. Waiting is never an attempt, and the timeout only counts from
the moment the call is dialled. A call that has waited 7 days without being
dialled is given up as error.
In their app
With In their app, the step rings the person your app has signed in (Identity and users), on their phones that use your app, with hosted ringing. It can ring them now, or book the call for a time, as a scheduled call.
| Setting | Meaning |
|---|---|
Who to ring (identity) | The value that holds the person's ID in your app, entry.user_id unless you choose another. |
Their ID's field (identity_field) | Which of your app's fields that ID is in: the one the agent's Memory & identity setting uses (user_id, email, phone or your own). The dashboard fills it in from the agent. Leave it out and the contact's own user_field is used, else user_id. |
When it rings (at) | Leave it out to ring now. Or a date-and-time value, like entry.preferred_time, or a wait, like {"delay": {"hours": 2}} (at most 30 days). A value that is empty for someone, or a time already past, rings now. |
Time zone (timezone) | Optional: the zone of a time written without its offset. Without one, the person's own time zone is used. |
A call in the app follows every rule of a call your app or your agent books: it rings only within the agent's calling hours, outside quiet hours, in the person's own time zone, and a booked time outside them moves to the next time that works. A booked call shows in the person's upcoming calls in your app, and in the agent's Scheduled calls. The timeout counts from when it rings, so a call booked for tomorrow is waited for from tomorrow. If you cancel the run, a call it booked that has not rung yet is cancelled too; a call that is ringing or live is never cut.
How it ended is in call.status, the same values as a phone call:
| In the app | call.status |
|---|---|
| Answered and talked | completed, with call.duration_s and call.execution_id, the conversation's id in Call History |
| Declined on their phone | busy |
| Rang and nobody answered (a second ring included, when the agent rings a missed call again) | no_answer |
| Rang, but couldn't ring again in time (rung too often today, declined a call recently) | no_answer, with the reason in call.error |
| Its time passed before it could ring at all (more than 10 minutes late) | failed, with call.error expired_unrung — nobody was asked, so it isn't "not answered" |
| No phone of theirs could be rung, or the ring failed | failed, with the reason in call.error (no_device when none could be rung) |
| Cancelled from Scheduled calls | cancelled |
When the call can never be placed as written, call.status is error and
call.error says why: no_identity (no ID for this person), invalid_identity
(an ID that names nobody), no_app_user (none of their phones has used your app
in 30 days), app_calls_not_set_up (your app can't be rung yet: see
Hosted ringing), too_soon (a booked time sooner than
the agent lets calls be booked) or invalid_time. An empty balance, a switched-off
agent, quiet hours, too many calls already booked for the person, or a time
further ahead than the agent books calls don't fail the step: the call is asked
for again later, and that is never an attempt. A booked time is fixed when the
step first runs, so asking again never moves it later; a time that only became
too soon while it waited moves to the soonest the agent allows. Calls booked into
quiet hours ring when they end, spread over two minutes so a list of them doesn't
ring at the same second.
One call, even when it reconnects
A call in the app that reconnects partway through is still one call. The step waits until the whole call is over, then reads the conversation that was really had.
Read call results
Reads the fields your agent's post-call analysis (Agent → Custom Analysis) found in the call its Call step placed. Put it right after that Call step, and pick the fields you want.
The analysis is ready a minute or so after an answered call, and the step waits for it, at most 10 minutes. For a call nobody answered there is nothing to read, so the step moves on at once with no fields. Set to The call or chat that started the run, it reads the call or the chat whose end started the run (Start when a call ends).
Check call history
Counts how many times the person was already called in a window before now, by any of your agents: lists, other workflows, scheduled calls and callbacks, and the API all count, and so do this run's own earlier calls. Put it before a Call, or make your Try again go back to it, and its paths decide whether to call: stop at three calls a week, stop once someone has answered, or go on. Nothing else changes: only a workflow with this step looks at the history.
| Setting | Meaning |
|---|---|
Whose calls (channel) | phone (the default): calls to their phone number. app: calls that rang them in your app. |
Their number (match) | By phone: the value that holds their number, entry.mobile_number unless you choose another. |
Their ID in your app (identity, identity_field) | In the app: the same as a Call in their app — the value holding their ID, and the field it is in. |
How far back (window) | Whole days or hours, from 1 hour to 90 days: {"days": 7}, {"hours": 12}. |
The person is found by their number (or, in the app, their ID in your app), never by the contact: the same person is a different contact in each list, and every one of those calls counts. Only calls that really went out count: not a call that was booked and never rang, or one refused before it was placed (an empty wallet, an agent switched off).
| Value | Means |
|---|---|
history.calls | Calls placed to them in the window. |
history.answered | Of those, the ones a person answered — not a voicemail. |
history.last_called_at | When the latest of them was placed. |
history.last_status | How it ended, in the words of call.status: completed, busy, no_answer, failed, voicemail or cancelled. Empty while that call is still going on. |
history.status | ok, or unavailable when the history couldn't be read just then. |
history.error | Why it is unavailable: no_person (no number, or no ID in your app, to look up), timeout or unreadable. |
Give “unavailable” a path
When the history can't be read, history.calls is empty, so a rule like
"at least 3" is false and the run would take Otherwise — the call. Add a path
for history.status is unavailable that ends the run, or waits and checks
again, so nobody is called without being counted.
HTTP request
One HTTP request to your system: push a qualified lead to your CRM, look a
customer up, book a slot. Setup is in
Connect your systems.
The answer is in response.status and in the values you map from its body. When
your system could not be reached, there is response.error. A 4xx or 5xx is
an answer like any other, so a path can act on it.
Wait for your system
Pauses the run until your system calls back, for example when a deal changes stage or a payment arrives, or until the timeout. How your system calls back is in Connect your systems.
| Setting | Meaning |
|---|---|
How to find the contact (correlation.payload_path) | Where your request names the contact, such as lead.phone. |
Compared with (correlation.matches) | The contact's value it must equal, entry.mobile_number unless you choose another. |
Values to keep (payload_map) | Names for values in your request, such as stage from lead.stage. |
Require a signature (require_signature) | Accept only signed requests. |
Wait up to (timeout_s) | 1 minute to 30 days, 1 day by default. |
callback.status is received or timeout, with your values beside it. A
path must say what happens on timeout.
Send a WhatsApp message
Sends a message from one of your connected WhatsApp numbers (Integrations → WhatsApp). Choose an approved template, with its language and the values for its placeholders, or a text. A text only reaches someone who wrote to your number in the last 24 hours; to start a conversation, use a template.
message.status is sent or failed, with message.id, or message.error
saying why: no number to send to, the channel needs reconnecting, or the code
WhatsApp gave.
Send an SMS and Send an email
Send with one of your messaging connections (see
Connect your systems):
an SMS with Twilio, an email with SendGrid or Resend. An SMS is up to 1,600
characters, sent to entry.mobile_number unless you choose another value. An
email has a subject and a text, sent to entry.email unless you choose another.
Both can use placeholders, and both set message.* as WhatsApp does.
Run an automation
Starts one of your automations (Workflows → Automations) with the values you
choose from the run. That brings everything an automation can do into a
workflow: Slack, HubSpot, Google Sheets, Notion, Salesforce, Stripe and the rest.
automation.status is started or failed; the automation then runs on its own.
Wait
Waits for a time (delay: days, hours and minutes, up to a year) or until a
moment (at). The moment can be a fixed time with its offset, or a value of
type timestamp with a time zone, such as a follow-up time your system sends
with the contact. It has one way out, its to.
Try again
Goes back to an earlier step, such as the Call, after a wait, for a limited number of attempts: 1 to 10, each with its own wait. It is the only way a workflow can go round in a loop. Once every attempt is used, its paths decide what happens next, usually an End.
When it goes back to a Call, the next call is booked for its time straight
away. Until it rings, it is under Upcoming in Call History, marked
Booked by a workflow, and it meets the same checks as any call when its
time comes: your balance, the agent being on, its calling hours and your
lines. It carries the same values the step would send at that time. If you
cancel it there, the run moves on with call.status cancelled. A call in
your app that has no time of its own is booked the same way, in the person's
upcoming calls.
End
Ends the run with a label, the name you will count it by (qualified,
paid, unreached), and an outcome: success, failure or neutral.
Each published version counts its runs by outcome, and every list shows its
totals.

