wait for
wait for
Suspend machine execution until an external event arrives. A wait for step pauses the current machine, serializes its state to the database, and resumes when a matching event or callback is received. This is the foundation for human-in-the-loop workflows, webhook callbacks, payment confirmations, approval gates, and any operation where the machine must yield control and resume later.
When to use
Use wait for when you need to:
- Wait for a webhook callback (payment confirmation, CI result)
- Implement human approval gates
- Suspend for an external system to complete an async operation
- Wait for a named event from another machine or system
Use ask ... from for synchronous calls that return immediately. Use launch to start async work you do not need to wait for. Use subscribes for event-driven reactive machines that process a continuous stream of events (rather than suspending mid-flow).
Syntax
wait for <name> event: "<event_name>" timeout: "<duration>" on_timeout: flow(<flow_name>) schema <field> as <type>, <modifiers>Callback variant
wait for <name> callback: true timeout: "<duration>" on_timeout: flow(<flow_name>) schema <field> as <type>, <modifiers>Configuration
| Config | Required | Description |
|---|---|---|
event | Yes (unless callback: true) | The named event to wait for. Matched against incoming events. |
callback | No | If true, generates a unique resume URL. External systems POST to this URL to resume the machine. |
timeout | No | Maximum wait duration (e.g., "24h", "7d", "30m"). If exceeded, the on_timeout flow runs. |
on_timeout | No | Flow to execute if the timeout expires. Typically handles cancellation or cleanup. |
schema | No | Validates the resume data structure before the machine continues. If validation fails, the resume is rejected. |
channel | No | Source channel for the event: "webhook", "chat", "api", etc. |
Resume data access
After the machine resumes, the data from the event or callback is available as steps.<name>.<field>:
wait for approval event: "approval_decision" schema approved as boolean, is required reviewer as text
// After resume, access the data:compute handle_decision { was_approved: steps.approval.approved, who_approved: steps.approval.reviewer }Callback metadata
When using callback: true, the step exposes a resume URL before suspension:
| Field | Description |
|---|---|
steps.<name>._resume_url | Unique URL for the external system to POST resume data |
steps.<name>._resume_token | The token portion of the resume URL |
Pass _resume_url to the external system so it knows where to send the callback.
Examples
Payment callback workflow
machine payment_workflow accepts order_id as text, is required amount as number, is required
responds with payment_status as text payment_id as text
implements action request_payment http method: "POST" url: "https://payments.example.com/charge" body: { order_id: input.order_id, amount: input.amount, callback_url: steps.wait_for_payment._resume_url }
wait for wait_for_payment callback: true timeout: "24h" on_timeout: flow(cancel_order) schema payment_id as text, is required status as text, is required
compute result { payment_status: steps.wait_for_payment.status, payment_id: steps.wait_for_payment.payment_id }
flows flow cancel_order action notify call machine: "@mashin/actions/notifications/send" message: "Payment timed out for order " + input.order_id compute result {payment_status: "timed_out", payment_id: null}Human approval gate
machine content_review accepts content as text, is required author as text, is required
implements ask pre_screen, using: "anthropic:claude-haiku-4" with task "Check this content for policy violations.\n\nContent: ${input.content}" returns flags as list auto_approve as boolean assuming flags: [] auto_approve: true
decide needs_review when steps.pre_screen.auto_approve compute auto_result {approved: true, reviewer: "auto"} otherwise wait for human_review channel: "chat" timeout: "72h" on_timeout: flow(escalate) schema approved as boolean, is required reviewer as text, is required notes as text
flows flow escalate launch notify_admin machine: "@myorg/alerts/send" message: "Content review timed out for " + input.authorWait for named event
wait for ci_result event: "ci.pipeline.complete" timeout: "1h" schema passed as boolean, is required build_id as text test_count as number failures as listHow suspension works
Machine Database External System | | | |-- wait for (suspend) ----->| | | (serialize state) |-- store run state -------->| | | (steps, context, position)| | | | | |<--- POST /resume/:token ---| | | (validate schema) | |<-- resume (deserialize) ---| | | (continue next step) | |The machine process shuts down during suspension. State is fully serialized to the database. When the resume event arrives, a new process is started with the deserialized state, and execution continues from the step after wait for.
Governance
Every wait for step is governed:
- Permission check: suspension and resumption are governed operations
- Behavioral ledger: the suspension event (including timeout, channel, and schema) is recorded
- Resume validation: the
schemablock validates incoming resume data before the machine continues. Invalid data is rejected. - Timeout enforcement: if a timeout is configured, the runtime schedules a timeout check. When the timeout fires, the
on_timeoutflow runs under normal governance.
Suspended machines consume no runtime resources (no process, no memory). They exist only as serialized state in the database until resumed.
Translations
| Language | Keyword |
|---|---|
| English | wait for |
| Spanish | espera |
| French | attend |
| German | wartet auf |
| Japanese | 待つ |
| Chinese | 等待 |
| Korean | 기다리다 |
See also
- launch - Start async work without waiting
- decide - Branch on whether to suspend or continue
- implements - The section where wait for steps live
- subscribes - Continuous event processing (vs. one-shot suspension)