Flow Best Practices

This section covers design patterns, optimization techniques, and best practices for building maintainable and efficient flows.

Note

AI Implementation Hint

When generating flows programmatically, follow three key rules: (1) Always set default_target_id on every branch action to handle unexpected input. (2) Always set loop_count on every goto action to prevent infinite loops. (3) Only set explicit id fields on actions that are referenced as targets by goto, branch, or condition_* actions.

Flow Design Principles

Keep Flows Focused

Principle: Single Responsibility

+------------------------------------------------------------------+
|                         BAD: Monolithic Flow                     |
+------------------------------------------------------------------+
| One giant flow that handles:                                     |
| - Language selection                                             |
| - Main menu                                                      |
| - Sales sub-menu                                                 |
| - Support sub-menu                                               |
| - Billing sub-menu                                               |
| - Queue logic                                                    |
| - Voicemail                                                      |
| - Survey                                                         |
|                                                                  |
| Problems:                                                        |
| - Hard to maintain                                               |
| - Hard to test                                                   |
| - Hard to reuse components                                       |
| - Changes affect everything                                      |
+------------------------------------------------------------------+

+------------------------------------------------------------------+
|                        GOOD: Modular Flows                       |
+------------------------------------------------------------------+
|                                                                  |
| Main Router Flow                                                 |
|   └── fetch_flow: Language Selection                             |
|         └── fetch_flow: Main Menu                                |
|               ├── fetch_flow: Sales Menu                         |
|               ├── fetch_flow: Support Menu                       |
|               └── fetch_flow: Billing Menu                       |
|                                                                  |
| Shared Flows:                                                    |
|   - Queue Wait Flow (reused by all menus)                        |
|   - Voicemail Flow (reused for after-hours)                      |
|   - Survey Flow (attached via on_complete_flow_id)               |
|                                                                  |
| Benefits:                                                        |
| - Each flow has one job                                          |
| - Easy to test individually                                      |
| - Reusable components                                            |
| - Changes are localized                                          |
+------------------------------------------------------------------+

Use Meaningful Action IDs

Action ID Best Practices:

+------------------------------------------------------------------+
|                            BAD                                   |
+------------------------------------------------------------------+
| {                                                                |
|   "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",                  |
|   "type": "talk",                                                |
|   ...                                                            |
| }                                                                |
|                                                                  |
| Debugging nightmare:                                             |
| "Flow stopped at action a1b2c3d4-e5f6-7890-abcd-ef1234567890"    |
| What does this action do?                                        |
+------------------------------------------------------------------+

+------------------------------------------------------------------+
|                           GOOD                                   |
+------------------------------------------------------------------+
| {                                                                |
|   "id": "welcome-greeting",                                      |
|   "type": "talk",                                                |
|   ...                                                            |
| }                                                                |
|                                                                  |
| {                                                                |
|   "id": "main-menu-branch",                                      |
|   "type": "branch",                                              |
|   ...                                                            |
| }                                                                |
|                                                                  |
| {                                                                |
|   "id": "invalid-input-retry",                                   |
|   "type": "goto",                                                |
|   ...                                                            |
| }                                                                |
|                                                                  |
| Debugging is clear:                                              |
| "Flow stopped at action main-menu-branch"                        |
| Immediately know where and what                                  |
+------------------------------------------------------------------+

Naming Convention:
+------------------------------------------------------------------+
| Format: {context}-{purpose}                                      |
|                                                                  |
| Examples:                                                        |
| - welcome-greeting                                               |
| - menu-input-receive                                             |
| - sales-branch                                                   |
| - invalid-retry-loop                                             |
| - after-hours-voicemail                                          |
+------------------------------------------------------------------+

Always Include Default Branches

Branch Best Practice:

+------------------------------------------------------------------+
|                            BAD                                   |
+------------------------------------------------------------------+
| {                                                                |
|   "type": "branch",                                              |
|   "option": {                                                    |
|     "variable": "voipbin.call.digits",                           |
|     "target_ids": {                                              |
|       "1": "option-1",                                           |
|       "2": "option-2"                                            |
|     }                                                            |
|   }                                                              |
| }                                                                |
|                                                                  |
| Problem: What happens if user presses 3, 4, 5, etc.?             |
| Flow falls through to next action unexpectedly                   |
+------------------------------------------------------------------+

+------------------------------------------------------------------+
|                           GOOD                                   |
+------------------------------------------------------------------+
| {                                                                |
|   "type": "branch",                                              |
|   "option": {                                                    |
|     "variable": "voipbin.call.digits",                           |
|     "default_target_id": "invalid-input-handler",                |
|     "target_ids": {                                              |
|       "1": "option-1",                                           |
|       "2": "option-2"                                            |
|     }                                                            |
|   }                                                              |
| }                                                                |
|                                                                  |
| All unexpected inputs are caught and handled                     |
+------------------------------------------------------------------+

Use Loop Counts

Goto Best Practice:

+------------------------------------------------------------------+
|                            BAD                                   |
+------------------------------------------------------------------+
| {                                                                |
|   "type": "goto",                                                |
|   "option": {                                                    |
|     "target_id": "menu-start"                                    |
|   }                                                              |
| }                                                                |
|                                                                  |
| Problem: Infinite loop possible                                  |
| User keeps entering invalid input forever                        |
+------------------------------------------------------------------+

+------------------------------------------------------------------+
|                           GOOD                                   |
+------------------------------------------------------------------+
| {                                                                |
|   "type": "goto",                                                |
|   "option": {                                                    |
|     "target_id": "menu-start",                                   |
|     "loop_count": 3                                              |
|   }                                                              |
| }                                                                |
|                                                                  |
| After 3 attempts, flow continues past goto                       |
| Add a "too many attempts" handler after the goto                 |
+------------------------------------------------------------------+

Performance Optimization

Minimize Webhook Calls

Webhook Optimization:

+------------------------------------------------------------------+
|                            BAD                                   |
+------------------------------------------------------------------+
| Every action sends a webhook:                                    |
|                                                                  |
| answer -> webhook -> talk -> webhook -> digits -> webhook        |
|                                                                  |
| Problems:                                                        |
| - Slows down flow execution                                      |
| - High load on your server                                       |
| - Increased latency for caller                                   |
+------------------------------------------------------------------+

+------------------------------------------------------------------+
|                           GOOD                                   |
+------------------------------------------------------------------+
| Strategic webhooks at key points:                                |
|                                                                  |
| answer -> talk -> digits -> branch -> webhook (with all data)    |
|                                                                  |
| Batch data in a single webhook:                                  |
| {                                                                |
|   "call_id": "${voipbin.call.id}",                               |
|   "caller": "${voipbin.call.source.target}",                     |
|   "selection": "${voipbin.call.digits}",                         |
|   "timestamp": "${voipbin.activeflow.tm_create}"                 |
| }                                                                |
+------------------------------------------------------------------+

Use Async Webhooks When Possible

Sync vs Async Webhooks:

+------------------------------------------------------------------+
|                     Sync (sync: true)                            |
+------------------------------------------------------------------+
| Flow waits for response before continuing                        |
|                                                                  |
| Use when:                                                        |
| - You need data from the response                                |
| - Response sets variables for branching                          |
| - Order of operations matters                                    |
|                                                                  |
| Example: Customer lookup before greeting                         |
+------------------------------------------------------------------+

+------------------------------------------------------------------+
|                    Async (sync: false)                           |
+------------------------------------------------------------------+
| Flow continues immediately, doesn't wait                         |
|                                                                  |
| Use when:                                                        |
| - Just logging/notification                                      |
| - Response data not needed                                       |
| - Background processing (analytics, etc.)                        |
|                                                                  |
| Example: Logging call events for analytics                       |
+------------------------------------------------------------------+

Optimize Audio Prompts

Audio Optimization:

+------------------------------------------------------------------+
|                     TTS vs Pre-recorded                          |
+------------------------------------------------------------------+

Use TTS (talk action) when:
+------------------------------------------------------------------+
| - Content is dynamic (names, numbers, dates)                     |
| - Frequent text changes                                          |
| - Multiple languages needed                                      |
| - Prototyping/development                                        |
+------------------------------------------------------------------+

Use Pre-recorded (play action) when:
+------------------------------------------------------------------+
| - Content is static                                              |
| - Professional voice quality needed                              |
| - Brand voice consistency important                              |
| - High-volume production traffic                                 |
+------------------------------------------------------------------+

Performance Comparison:
+------------------------------------------------------------------+
| TTS: ~100-300ms generation time per phrase                       |
| Pre-recorded: ~50ms to start playback                            |
+------------------------------------------------------------------+

Keep Variable Names Short

Variable Naming:

+------------------------------------------------------------------+
|                            BAD                                   |
+------------------------------------------------------------------+
| ${customer_information.primary_contact.phone_number.country_code}|
|                                                                  |
| Problems:                                                        |
| - Hard to read in JSON                                           |
| - More data in database                                          |
| - Prone to typos                                                 |
+------------------------------------------------------------------+

+------------------------------------------------------------------+
|                           GOOD                                   |
+------------------------------------------------------------------+
| ${customer.phone}                                                |
| ${customer.country}                                              |
|                                                                  |
| Or use namespaces:                                               |
| ${cust.phone}                                                    |
| ${cust.tier}                                                     |
+------------------------------------------------------------------+

Error Handling Patterns

Graceful Degradation

Fallback Pattern:

+------------------------------------------------------------------+
|                     Handle Service Failures                      |
+------------------------------------------------------------------+

{
  "name": "Resilient Flow",
  "actions": [
    {
      "type": "answer"
    },
    {
      "id": "try-personalization",
      "type": "webhook_send",
      "option": {
        "sync": true,
        "uri": "https://your-api.com/customer",
        "method": "POST",
        "data_type": "application/json",
        "data": "{\"phone\": \"${voipbin.call.source.target}\"}"
      }
    },
    {
      "type": "condition_variable",
      "option": {
        "condition": "!=",
        "variable": "customer.name",
        "value_type": "string",
        "value_string": "",
        "false_target_id": "generic-greeting"
      }
    },
    {
      "id": "personalized-greeting",
      "type": "talk",
      "option": {
        "text": "Hello ${customer.name}, welcome back.",
        "language": "en-US"
      }
    },
    {
      "type": "goto",
      "option": {
        "target_id": "main-menu"
      }
    },
    {
      "id": "generic-greeting",
      "type": "talk",
      "option": {
        "text": "Hello, welcome to our service.",
        "language": "en-US"
      }
    },
    {
      "id": "main-menu",
      "type": "talk",
      "option": {
        "text": "Press 1 for sales, 2 for support.",
        "language": "en-US"
      }
    }
  ]
}

Timeout Handling

Input Timeout Pattern:

+------------------------------------------------------------------+
|                     Handle No Response                           |
+------------------------------------------------------------------+

{
  "actions": [
    {
      "id": "prompt",
      "type": "talk",
      "option": {
        "text": "Press 1 or 2.",
        "language": "en-US"
      }
    },
    {
      "type": "digits_receive",
      "option": {
        "duration": 5000,
        "length": 1
      }
    },
    {
      "type": "condition_variable",
      "option": {
        "condition": "!=",
        "variable": "voipbin.call.digits",
        "value_type": "string",
        "value_string": "",
        "false_target_id": "no-input"
      }
    },
    {
      "type": "branch",
      "option": {
        "variable": "voipbin.call.digits",
        "default_target_id": "invalid-input",
        "target_ids": {
          "1": "option-1",
          "2": "option-2"
        }
      }
    },
    {
      "id": "no-input",
      "type": "talk",
      "option": {
        "text": "I didn't receive any input.",
        "language": "en-US"
      }
    },
    {
      "type": "goto",
      "option": {
        "target_id": "prompt",
        "loop_count": 2
      }
    },
    {
      "type": "talk",
      "option": {
        "text": "Goodbye.",
        "language": "en-US"
      }
    },
    {
      "type": "hangup"
    },
    {
      "id": "invalid-input",
      "type": "talk",
      "option": {
        "text": "Invalid selection.",
        "language": "en-US"
      }
    },
    {
      "type": "goto",
      "option": {
        "target_id": "prompt",
        "loop_count": 2
      }
    }
  ]
}

Maintainability

Document Your Flows

Flow Documentation:

+------------------------------------------------------------------+
|                     Use Name and Detail Fields                   |
+------------------------------------------------------------------+

{
  "name": "Main IVR Menu - English",
  "detail": "Primary entry point for English callers. Routes to Sales, Support, or Billing queues. Uses business hours check. Last updated: 2024-01-15",
  "actions": [...]
}

Naming Convention:
+------------------------------------------------------------------+
| Format: {Function} - {Context}                                   |
|                                                                  |
| Examples:                                                        |
| - "Main IVR Menu - English"                                      |
| - "Support Queue Wait Flow"                                      |
| - "After Hours Voicemail"                                        |
| - "Post-Call Survey - Premium Customers"                         |
+------------------------------------------------------------------+

Version Your Flows

Versioning Strategy:

+------------------------------------------------------------------+
|                     Flow Versioning                              |
+------------------------------------------------------------------+

Option 1: Include version in name
{
  "name": "Main IVR v2.1",
  "detail": "Version 2.1 - Added Spanish language option"
}

Option 2: Keep multiple flow versions
+------------------------------------------------------------------+
| Production: "main-ivr-prod"     <- Stable, live traffic          |
| Staging:    "main-ivr-staging"  <- Testing new features          |
| Dev:        "main-ivr-dev"      <- Development/experiments       |
+------------------------------------------------------------------+

Promotion workflow:
1. Develop in dev flow
2. Test in staging flow
3. Copy to production flow when ready

Test in Isolation

Testing Approach:

+------------------------------------------------------------------+
|                     Modular Testing                              |
+------------------------------------------------------------------+

Each sub-flow can be tested independently:

1. Language Selection Flow
   - Test: All language options work
   - Test: Default fallback works

2. Main Menu Flow
   - Test: All branch options route correctly
   - Test: Invalid input handling
   - Test: Timeout handling

3. Support Queue Flow
   - Test: Queue join works
   - Test: Wait music plays
   - Test: Timeout routes to voicemail

4. Voicemail Flow
   - Test: Recording starts
   - Test: Recording stops on timeout
   - Test: Recording stops on key press

Security Best Practices

Validate External Data

Input Validation:

+------------------------------------------------------------------+
|                     Validate Webhook Responses                   |
+------------------------------------------------------------------+

When using data from webhook_send responses:

BAD:
{
  "type": "talk",
  "option": {
    "text": "Calling ${customer.phone}",   <- Could be anything!
    "language": "en-US"
  }
}

GOOD:
{
  "type": "condition_variable",
  "option": {
    "condition": "!=",
    "variable": "customer.phone",
    "value_type": "string",
    "value_string": "",
    "false_target_id": "no-phone-error"
  }
}
... then use customer.phone

Protect Sensitive Data

Data Protection:

+------------------------------------------------------------------+
|                     Sensitive Information                        |
+------------------------------------------------------------------+

Don't log sensitive data in webhooks:

BAD:
{
  "type": "webhook_send",
  "option": {
    "data": "{\"credit_card\": \"${customer.card}\"}"
  }
}

GOOD:
{
  "type": "webhook_send",
  "option": {
    "data": "{\"customer_id\": \"${customer.id}\", \"action\": \"payment_attempt\"}"
  }
}

Look up sensitive data server-side using customer_id

Rate Limiting Awareness

Rate Limiting:

+------------------------------------------------------------------+
|                     API Rate Limits                              |
+------------------------------------------------------------------+

Be aware of limits on:
- Webhook requests to your server
- VoIPBIN API calls
- TTS generation requests

Design flows to minimize API calls:
+------------------------------------------------------------------+
| - Batch data in single webhooks                                  |
| - Cache customer data for session duration                       |
| - Use variables instead of repeated lookups                      |
+------------------------------------------------------------------+

Checklist: Flow Review

Before deploying a new flow, verify:

Pre-Deployment Checklist:

Structure:
[ ] All action IDs are meaningful and unique
[ ] All branch actions have default_target_id
[ ] All goto actions have loop_count
[ ] Flow name and detail are descriptive
[ ] Modular design (sub-flows where appropriate)

Error Handling:
[ ] Invalid input is handled
[ ] Timeouts are handled
[ ] Service failures have fallbacks
[ ] Caller hangup is considered

Performance:
[ ] Webhook calls are minimized
[ ] Async webhooks used where possible
[ ] No unnecessary loops
[ ] Variables are concise

Security:
[ ] No sensitive data in webhooks
[ ] External data is validated
[ ] Recording consent is obtained (if required)

Testing:
[ ] All branches tested
[ ] Timeout scenarios tested
[ ] Error scenarios tested
[ ] End-to-end call tested

Documentation:
[ ] Flow name describes purpose
[ ] Flow detail includes version/date
[ ] Complex logic is commented (in detail field)