waitstableWait
Pauses the flow for a fixed duration. The wait is interrupted if the call ends or bridges (e.g. an agent picks up while the call is queued). Use it to exp…
What it does
Pauses the flow for a fixed number of seconds. The worker schedules a
delayed BullMQ job (via scheduleFlowWait) and parks the run on this
node; when the job fires, the flow follows on['wait.elapsed']. While
the wait is in progress the call is still live — the caller hears
whatever was last started (hold music, a long playback, silence). The
duration is clamped to the inclusive range 1-3600 seconds; a value
outside that range is logged and the node skips without scheduling
anything.
The wait is also interrupted by call lifecycle events. If the caller
hangs up while we're waiting, call.hangup interrupts on this node
and follows the configured transition (typically to a cleanup or
end). If the call bridges — for example because the caller was
parked in a queue and an agent just picked up — call.bridged fires
on this node and you can route to a "got connected, stop the timer"
target. The pending wait job's id is stashed at vars._waitJobId;
when the flow leaves the wait node (via any transition), the worker
removes the delayed job through cancelFlowWait so it doesn't fire
after the fact. If the cancel races the timer firing, the handler
falls back to a no-op because the run has already moved past the node.
When to use it
- Implement a max-wait-then-voicemail fallback in a queue:
enqueue → playAudio (loop) → wait 600 → voicemail. The bridge interrupt on the wait jumps you to "connected" if an agent picks up first - Pause briefly between actions in an outbound call campaign — e.g.
speak a prompt, then
wait 2before listening for digits, to avoid clipping the start of the response - Build periodic announcements during a long hold:
wait 60 → say "You are still in queue" → wait 60 → say ...— wired in a loop - Stagger automated messages so they don't all fire at once when many callers enter the same hold experience
- Add a small grace period after a setVar / HTTP call before the next playback, so the caller doesn't feel the abrupt switch
Configuration
Pauses the flow for a fixed duration. The wait is interrupted if the call ends or bridges (e.g. an agent picks up while the call is queued). Use it to express max-wait-then-fallback in a queue: enqueue → playAudio (loop) → wait → voicemail.
| Field | Label | Type | Required | Default | Notes |
|---|---|---|---|---|---|
durationSecs | Duration (seconds) | number | Required | 300 | How long to wait before firing the wait.elapsed transition. Capped at 3600 (1 hour). |
Outgoing events: wait.elapsed, call.bridged, call.hangup
Examples
Max-wait fallback in a queue
The wait fires after ten minutes; if an agent bridges first, the
call.bridged interrupt routes to end and the worker cancels the
pending wait job on the way out (best-effort — see Gotchas).
{
"id": "max-wait",
"type": "wait",
"config": { "durationSecs": 600 },
"on": {
"wait.elapsed": "voicemail",
"call.bridged": "end",
"call.hangup": "end"
}
}
Tiny gap between TTS and digit-collect
A two-second gap between a TTS prompt and the gatherDigits step gives the caller time to start speaking before the DTMF window opens.
{
"id": "settle",
"type": "wait",
"config": { "durationSecs": 2 },
"on": { "wait.elapsed": "collect-digits", "call.hangup": "end" }
}
Gotchas
- Duration is clamped to 1-3600 seconds. A
durationSecsof 0 or a negative number logs a warning and skips the node entirely — no wait, no transition fired. A value above 3600 is silently capped at 3600 (one hour). Use a chain of multiplewaitnodes for longer holds, or rethink the design. - Interrupt events fire on the wait node, not the upstream
enqueue / dial. When
call.bridgedorcall.hanguparrive while the wait is in flight, the worker readsnode.on[event]on the wait node itself — wire those edges directly. Forgetting to wirecall.hanguphere means a caller who hangs up during the wait stays "running" until the BullMQ job eventually fires and tries to advance into a node whose context is already gone. - The pending wait job is cancelled when the flow leaves the
wait node. If a
call.hangup,call.bridged, or any other event interrupts the wait before the timer fires, the worker'sleaveNodehook callscancelFlowWait(_waitJobId)and removes the delayed BullMQ job. Cancellation is best-effort: if BullMQ has already moved the job to active by the time we try to remove it (sub-second race), the no-op fallback still applies — the handler runs, seesstate.currentNodeIdhas moved on, and advances nothing. Stale jobs from runs created before this fix shipped will still no-op until they fire and age out. vars._waitJobIdis reserved bookkeeping. Don't write to it from a customScript. The variable is overwritten on every wait node entry; only the most recent wait's job id is tracked.- Real-world latency adds 100-500ms. BullMQ delayed jobs are
best-effort, not real-time. A
durationSecs: 1wait can land 200ms late under worker load. For tight timing inside a single call, prefer the catalog's built-in inter-digit / playback timeouts where they exist.
