Purpose: Complete reference guide for rebuilding the IVR Flow system in OmniFlow


1. IVR ARCHITECTURE OVERVIEW

Database Schema

Core Tables:

ivr_flows – Master flow definitions

ivr_nodes – Individual flow nodes

ivr_node_connections – Flow logic connections

ivr_executions – Runtime execution tracking

ivr_logs – Detailed execution logs

Node Types & Configurations

Each node type has specific config (JSONB) fields:

1. WELCOME Node – Play greeting message

{
  "message": "Welcome to OmniFlow",
  "voice": "alice",
  "language": "en-US",
  "audio_url": null,
  "merge_fields": {"company_name": "OmniFlow"}
}

2. MENU Node – Present options and gather input

{
  "message": "Main menu",
  "voice": "alice",
  "language": "en-US",
  "menu_options": [
    {"key": "1", "label": "Sales"},
    {"key": "2", "label": "Support"}
  ],
  "timeout_seconds": 5,
  "max_retries": 3
}

3. INPUT Node – Collect digits from caller

{
  "message": "Please enter your account number",
  "voice": "alice",
  "input_type": "digits",
  "max_digits": 10,
  "finish_on_key": "#",
  "timeout_seconds": 5
}

4. TRANSFER Node – Route call to agent/phone

{
  "message": "Transferring you now",
  "transfer_type": "agent",  // or "external", "warm", "cold"
  "transfer_to": "+1234567890",  // or "agent_queue"
  "voice": "alice"
}

5. CONDITION Node – Branching logic

{
  "condition_field": "caller_id",
  "condition_operator": "equals",  // or "contains", "greater_than"
  "condition_value": "+1234567890"
}

6. HANGUP Node – End call

{
  "message": "Thank you for calling. Goodbye.",
  "voice": "alice"
}

Connection Types

Connections define flow logic between nodes:


2. CORE API ENDPOINTS

IVR Flow Management (/ivr-flows)

POST /ivr-flows – Create new flow

  {
    "name": "Main IVR",
    "description": "Customer service flow",
    "nodes": [...],
    "connections": [...],
    "entry_point_node_id": "uuid"
  }

GET /ivr-flows – List all flows (paginated)

GET /ivr-flows/list – Simple list (no pagination)

GET /ivr-flows/{flow_id} – Get single flow

PUT /ivr-flows/{flow_id} – Update flow

DELETE /ivr-flows/{flow_id} – Delete flow

POST /ivr-flows/{flow_id}/set-default – Set as default

POST /ivr-flows/test?flowId={flow_id} – Test flow

IVR Execution Webhook (/twilio-ivr-webhook)

POST /twilio-ivr-webhook – Twilio callback handler

Actions Explained:

IVR Analytics Endpoints

GET /call-analytics/ivr-flow/{flow_id} – Flow-specific analytics

GET /call-analytics/ivr-campaign/{campaign_id} – Campaign IVR stats


3. IVR EXECUTION ENGINE (ivr_engine.py)

Core Engine Class: IVRExecutionEngine

Initialization:

self.base_webhook_url = "https://omnicallflow.com/api/twilio-ivr-webhook"  # Production
# or DEV: "https://api.databutton.com/_projects/{id}/dbtn/devx/app/routes/twilio-ivr-webhook"

TwiML Generation Process

Main Flow:

  1. generate_flow_twiml() – Entry point
  2. _generate_node_twiml() – Route to node type handler
  3. Node-specific generators (welcome, menu, input, transfer, etc.)
  4. Return TwiML XML string to Twilio

Key Design Pattern: Sequential TwiML Responses

Instead of inline processing, nodes use <Redirect> to chain TwiML responses:

<!-- Welcome Node TwiML -->
<Response>
  <Say voice="alice">Welcome to OmniFlow</Say>
  <Redirect method="POST">
    https://omnicallflow.com/api/twilio-ivr-webhook?flow_id=xxx&node_id=yyy&action=flow
  </Redirect>
</Response>

This creates separate HTTP requests for each node, allowing:

Node-Specific TwiML Generators

Welcome Node (_generate_welcome_twiml):

Menu Node (_generate_menu_twiml):

Input Node (_generate_input_twiml):

Transfer Node (_generate_transfer_twiml):

Condition Node (_generate_condition_twiml):

Hangup Node (_generate_hangup_twiml):

Connection Resolution

_find_connection_target():

# Example: Find next node for menu keypress "1"
next_node = _find_connection_target(
    current_node=menu_node,
    flow=flow,
    condition_type="keypress",
    condition_value="1"
)

Searches ivr_node_connections for matching:

Returns the connected to_node_id node object.

Webhook Processing (process_webhook_response)

Flow:

  1. Extract action from query params
  2. Load flow from database (get_ivr_flow_by_id)
  3. Parse Twilio request (form data):
  1. Route based on action:
  1. Generate next TwiML response

4. TESTING STRATEGY

Test Endpoints

1. /ivr-flows/test?flowId={uuid} (Primary testing endpoint)

  {
    "flow_id": "uuid",
    "test_phone": "+1234567890",
    "start_node_id": "optional-uuid"
  }
  1. Validates flow exists and user has access
  2. Gets tenant’s voice service (Twilio credentials)
  3. Makes outbound call via Twilio
  4. Directs call to IVR webhook with ?action=start
  {
    "success": true,
    "message": "Test call initiated",
    "call_sid": "CA123...",
    "test_url": "https://omnicallflow.com/api/twilio-ivr-webhook?flow_id=xxx&action=start"
  }

2. /test-ivr-webhook/... (Debug endpoints)

3. /test-auto-dialer/... (Auto-dialer IVR testing)

4. /webhook-debugging/debug-ivr-webhook

  1. Logs all request params (query, form, headers)
  2. Forwards to actual IVR webhook
  3. Logs response TwiML
  4. Stores call trace for debugging

Testing Workflow

Step 1: Create Test Flow

  1. Navigate to /ivr-flows page
  2. Create flow with nodes: Welcome → Menu → Transfer
  3. Set connections: Menu “1” → Transfer to agent
  4. Save flow

Step 2: Test in Editor

  1. Click “Test Flow” button
  2. Enter test phone number
  3. System makes real call
  4. Answer phone and interact with IVR
  5. Verify menu selections work
  6. Confirm transfers execute

Step 3: Debug Issues

  1. Check /call-logs for call_sid
  2. Query ivr_executions table for execution trace
  3. Check ivr_logs for detailed event log
  4. Use /webhook-debugging/debug-ivr-webhook for TwiML inspection

Step 4: Production Testing

  1. Set flow as default: POST /ivr-flows/{id}/set-default
  2. Make inbound call to company number
  3. Verify default IVR flow triggers
  4. Test all menu paths
  5. Monitor analytics: GET /call-analytics/ivr-flow/{id}

Common Testing Issues & Fixes

Issue 1: “Flow not found” error

Issue 2: Menu selections not working

Issue 3: Agent transfer fails

Issue 4: Infinite redirect loop

Issue 5: TwiML application error


5. COMPLETE WORKFLOWS

Workflow 1: Create IVR Flow

Frontend (IVRFlows page):

  1. User clicks “Create Flow”
  2. Visual editor allows dragging nodes
  3. User connects nodes with lines
  4. User configures each node (messages, options)
  5. User sets entry point node

API Call:

const response = await brain.ivr_flows_create({
  name: "Main IVR",
  description: "Customer service",
  nodes: [
    {
      node_type: "welcome",
      name: "Welcome",
      config: {
        message: "Welcome to OmniFlow",
        voice: "alice"
      },
      position_x: 100,
      position_y: 100
    },
    {
      node_type: "menu",
      name: "Main Menu",
      config: {
        message: "Main menu",
        menu_options: [
          {key: "1", label: "Sales"},
          {key: "2", label: "Support"}
        ]
      },
      position_x: 300,
      position_y: 100
    }
  ],
  connections: [
    {
      from_node_id: "welcome-uuid",
      to_node_id: "menu-uuid",
      condition_type: "default"
    },
    {
      from_node_id: "menu-uuid",
      to_node_id: "transfer-sales-uuid",
      condition_type: "keypress",
      condition_value: "1"
    }
  ],
  entry_point_node_id: "welcome-uuid"
});

Backend Processing:

  1. Validate flow structure (no orphan nodes, valid entry point)
  2. Begin database transaction
  3. Insert into ivr_flows
  4. Insert all nodes into ivr_nodes
  5. Insert all connections into ivr_node_connections
  6. Commit transaction
  7. Return complete flow object

Workflow 2: Execute IVR Flow (Inbound Call)

Step 1: Call Arrives

Step 2: Routing Decision

# Check for default IVR flow
default_flow = await get_default_ivr_flow_for_tenant(tenant_id)
if default_flow:
    ivr_url = f"{base_url}/twilio-ivr-webhook?flow_id={default_flow.id}&node_id={entry_point}&action=start"
    # Redirect to IVR

Step 3: IVR Execution Begins

Step 4: Node Processing

Step 5: User Input

Step 6: Transfer

Step 7: Completion

Workflow 3: Campaign with IVR

Campaign Creation:

{
  "name": "Sales Campaign",
  "type": "voice",
  "voice_campaign_type": "ivr",  // NOT "script"
  "voice_ivr_flow_id": "uuid-of-flow",
  "settings": {
    "voice_campaign_type": "ivr",
    "voice_ivr_flow_id": "uuid-of-flow"
  }
}

Auto-Dialer Execution:

  1. Progressive dialer makes call
  2. Call connects
  3. POST /auto-dialer-webhook?campaign_id=xxx
  4. Webhook reads campaign settings
  5. Detects voice_campaign_type == "ivr"
  6. Generates TwiML redirect to IVR webhook
  7. IVR flow executes normally

Critical Code:

# In auto-dialer-webhook
settings = json.loads(campaign['settings'])
campaign_type = settings.get('voice_campaign_type', 'agent')
ivr_flow_id = settings.get('voice_ivr_flow_id')

if campaign_type == 'ivr' and ivr_flow_id:
    # Route to IVR
    flow = await get_ivr_flow_by_id(ivr_flow_id)
    entry_node = flow.entry_point_node_id
    ivr_url = f"{base_url}/twilio-ivr-webhook?flow_id={ivr_flow_id}&node_id={entry_node}&action=start"
    twiml = f'<Response><Redirect>{ivr_url}</Redirect></Response>'
else:
    # Route to agent
    twiml = generate_agent_dial_twiml()

6. INTEGRATION POINTS

Twilio Integration

Required Configuration:

  1. Twilio Account SID + Auth Token (in user_voice_settings)
  2. Twilio Phone Number
  3. TwiML App SID (for softphone)

Webhook Configuration:

TwiML Verbs Used:

Campaign Integration

Voice Campaign Types:

Campaign Settings Storage:

{
  "voice_campaign_type": "ivr",
  "voice_ivr_flow_id": "uuid",
  "voice_script": null,  // Not used for IVR
  "voice_phone_number": "+1234567890"
}

Call Routing Integration

Routing Priority:

  1. Check for bypass_ivr flag (manual calls)
  2. Check for default IVR flow (is_default=true)
  3. If default IVR exists, redirect to IVR webhook
  4. Otherwise, route to agent assignment

Default IVR Flow Logic:

# In call_routing_webhook
if not bypass_ivr:
    default_flow = await get_default_ivr_flow_for_tenant(tenant_id)
    if default_flow:
        return redirect_to_ivr(default_flow)

# Fallback to agent routing
return assign_to_available_agent()

Analytics Integration

Data Collection:

Metrics Calculated:


7. KEY TECHNICAL DECISIONS

Why Sequential TwiML (Redirect Pattern)?

Original Problem: Inline TwiML generation created massive XML responses.

Solution: Each node returns TwiML that redirects to next node.

Benefits:

Why JSONB for flow_data?

Purpose: Denormalized cache of complete flow structure.

Benefits:

Tradeoff: Must update both flow_data and nodes/connections tables.

Why tenant_id on executions/logs?

Purpose: Multi-tenant data isolation.

Benefits:

Why is IVR webhook unprotected?

Reason: Twilio can’t send authentication headers.

Security:


8. REBUILD CHECKLIST

Phase 1: Database Setup

Phase 2: Core Engine

Phase 3: API Endpoints

Phase 4: Frontend

Phase 5: Integration

Phase 6: Testing

Phase 7: Monitoring & Analytics


9. COMMON PITFALLS & SOLUTIONS

Pitfall 1: Circular flows

Pitfall 2: Missing entry point

Pitfall 3: Orphan nodes

Pitfall 4: Invalid TwiML XML

Pitfall 5: Agent not available

Pitfall 6: IVR not triggering for campaigns


END OF GUIDE

Generated: 2025-10-13
Purpose: Rebuild reference for OmniFlow IVR Flow system

Leave a Reply

Your email address will not be published. Required fields are marked *