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