SignalWire Call Flow Builder
CFB is the drag-and-drop visual editor for voice flows. Lives under the Tools tab in the SignalWire portal. Generates valid SWML under the hood, so any flow built in CFB is equivalent to a hand-written script. Useful when non-technical team members need to edit flows, or when the flow is simple enough that visual debugging beats a code review.
Node taxonomy
Starting node (always present)
| Node | Description | |---|---| | Handle Call | Entry point for every incoming call. Cannot be deleted. Cannot be duplicated. |
Action nodes
| Node | Description |
|---|---|
| Answer Call | Marks the call answered. Optional — most nodes auto-answer when connected after Handle Call. |
| Hang Up Call | Disconnects. Reason field is for logs only. |
| Play Audio or TTS | Plays audio URL, ringtone (ring:5:jp), silence (silence:5), or TTS. Supports SSML and variables. |
| Send SMS | Sends an SMS mid-flow. Fields: To, From, Text. Supports variables. |
Forwarding nodes
| Node | Description | |---|---| | Forward to Phone | Forwards to phone(s) or SIP endpoint(s). Outputs: Success, No Answer, Busy, Decline, Error. Supports sequential or simultaneous dialing. Enable Whisper runs SWML before connecting. |
Recording nodes
| Node | Description |
|---|---|
| Start Call Recording | Background dual-channel recording. Fields: Name, Stereo, Beep, Terminators, Format. Auto-stops on disconnect. |
| Stop Call Recording | Stops a named recording. URL available as %{record_call_url}. |
| Voicemail Recording | Async voicemail record. Fields: Stereo, Beep, Terminators, Max Length, Initial/End Silence Timeout, Format. URL via %{record_url}. |
Input nodes
| Node | Description |
|---|---|
| AI Agent | Connects the call to a SignalWire AI Agent Resource. Single setting: AI Agent Name (dropdown populated from Resources). Handles NLP, sentiment, SWAIG, multi-turn dialogue. |
| Gather Input | Collects DTMF and/or speech. Outputs: one connector per input option + Unknown + No Input. %{prompt_value} holds detected speech for the Unknown path. |
| Request | HTTP GET/POST/PUT/DELETE. Headers and Body configurable. Response in %{request_response_body} or %{request_response.<field>}. Outputs: user-defined conditions + Else + Failure. |
Decision / data nodes
| Node | Description |
|---|---|
| Conditions | JavaScript if/else-if logic over %{variable} expressions. JS operators (&&, \|\|, ==, .slice()) work. |
| Execute SWML | Fetches and executes a remote SWML document. Passes Params + Meta. Return values via %{return_value.<field>}. |
| Set Variables | Creates named variables accessible as %{vars.<key>} for the rest of the flow. |
| Unset Variables | Clears previously set variables. |
AI Agent node — what it does
Replaces an entire IVR tree with natural-language handling. Setting: AI Agent Name (dropdown from Resources).
Capabilities the connected agent handles:
- Natural language processing (free-form speech understanding)
- Sentiment analysis
- SWAIG functions (CRM lookup, SMS, transfers)
- Multi-turn dialogue with context switching
Prerequisites — must exist before the node will work:
- Resources → + Add New → AI Agent → Custom AI Agent.
- Configure system prompt, persona, SWAIG functions.
- Fund the account (AI agents are billed per minute).
- The agent name will appear in the AI Agent node dropdown.
Variables reference
All variables use the form %{<variable>}.
Built-in call variables (always available)
| Variable | Description |
|---|---|
| %{call.from} | Caller's number |
| %{call.to} | Number dialed |
| %{call.direction} | Currently always inbound |
| %{call.call_id} | Unique call ID |
| %{call.state} | Current call state |
| %{call.type} | Call type |
Request node variables
| Variable | Description |
|---|---|
| %{request_response_body} | Full response body |
| %{request_response.<field>} | Specific JSON field |
| %{vars.request_response.<field>} | String field for conditional expressions |
Recording variables
| Variable | Description |
|---|---|
| %{record_call_url} | URL of stopped Call Recording |
| %{record_url} | URL of Voicemail Recording |
Gather Input variables
| Variable | Description |
|---|---|
| %{prompt_value} | Speech detected on the Unknown output path |
Custom (Set Variables node)
- Access with
%{vars.<key>}or%{<key>}. - JS expressions work:
%{call.from.slice(2,5)}extracts the area code. - String comparisons in conditions:
%{vars.request_response.dayOfWeek == 'Thursday'}.
Building a flow
- Dashboard → Tools tab → Call Flow Builder.
- Add New → name the flow → Save.
- More Options → Edit to open the canvas.
- The canvas starts with a single Handle Call node — the entry point.
- Drag nodes from the left panel.
- Connect them: click-drag from output (right side) to input (left side).
- Click a node to open its config panel.
- Click Deploy to make the flow live.
- Assign the flow to a phone number.
Every node has one input on the left and one or more outputs on the right. Multiple outputs = branching logic.
Assigning a flow to a DID
- Phone Numbers in the Dashboard.
- Select the number → edit Call Handler.
- Set handler to Call Flow → select the flow by name.
- Save.
Flows can also be assigned to SIP Addresses and Domain Applications the same way.
Versioning
- Version History (top-left of CFB) shows all versions with timestamps.
- Each Deploy creates a new version (1.0, 2.0, 3.0…).
- Roll back via Version History → select old version → Restore.
- Save without deploying to test changes safely.
CFB vs SWML vs Python SDK
| Situation | Use | |---|---| | Simple IVR, AI handoff, voicemail, basic branching | CFB | | Non-technical team members editing flows | CFB | | Visual debugging matters more than code review | CFB | | Complex conditionals, loops, dynamic SWML | SWML directly | | Git-versioned, programmatically generated flows | SWML directly | | Real-time call control, outbound dialing, browser SDK | Python / Node SDK | | Hybrid: visual flow + custom logic | CFB + Execute SWML node pointing at hosted SWML |
Common patterns
Basic AI Agent (simplest)
Handle Call → Answer Call → AI Agent: "My Agent"
IVR with routing
Handle Call → Answer Call → Gather Input (Press 1=Sales, 2=Support)
→ [Sales] → Forward to Phone: sales_number
→ [Support] → Forward to Phone: support_number
→ [No Input] → Play TTS "No input received" → Hang Up
AI Agent with recording
Handle Call → Answer Call → Start Call Recording → AI Agent: "My Agent"
Voicemail with SMS notification
Handle Call → Answer Call → Play TTS "Leave a message after the beep"
→ Voicemail Recording (beep on, terminator=#)
→ Send SMS: "New voicemail from %{call.from}: %{record_url}"
Business hours routing (API-based)
Handle Call → Answer Call → Request (GET timeapi.io)
→ Conditions: %{request_response.hour} >= 9 && %{request_response.hour} < 17
→ [Open] → AI Agent or Forward to Phone
→ [Else] → Play TTS "We're closed" → Voicemail Recording
Loop guard on Gather Input
Handle Call → Answer Call → Set Variables: loop_count = 0
→ Gather Input
→ [No Input] → Set Variables: loop_count = %{loop_count} + 1
→ Conditions: %{loop_count} >= 2
→ [True] → Hang Up
→ [Else] → back to Gather Input
Preserve caller ID on forward
Handle Call → Forward to Phone (From: %{call.from}, To: +15551234567)
→ [Success] → (connected)
→ [No Answer / Busy / Decline / Error] → Hang Up
Multi-request flow (variable persistence)
Handle Call → Answer Call → Request: API #1
→ Set Variables: my_data = %{request_response.field}
→ Request: API #2 (overwrites %{request_response}; %{vars.my_data} still readable)
→ Play TTS: "Result: %{vars.my_data}"
Anti-patterns
- Not creating the AI Agent Resource before adding the AI Agent node — the dropdown will be empty.
- Removing Handle Call — it can't be removed. Stop trying.
- Treating Answer Call as mandatory — it's optional; other nodes auto-answer.
- Using
%{request_response.<field>}after a second Request node — the second call overwrites the variable. Persist with Set Variables. - Wiring a Gather Input No Input output back to itself with no counter — infinite loop.
- Deploying without testing — save first, then Deploy after verification.
- Pointing Execute SWML at a URL that returns HTML — it must return JSON or YAML.
- Assigning a flow to a number before deploying — the flow won't be selectable until deployed.