Skip to main content
Routes the workflow to different paths based on AI analysis or your own routing logic.

Decision modes

Choose between two decision types:
  • AI-Powered: AI interprets user intent from conversation
  • Function (Custom Code): Run your own routing logic - business rules, data checks, API calls, etc.

Configuration

Common parameters

  • id: Unique node identifier
  • conditions: List of possible paths with labels and descriptions

AI-powered mode

  • decision_type: Set to "ai" (default)
  • provider_model_name: AI model to use for decision making
  • llm_temperature: Model creativity, 0.0-1.0 (default: 0.0)
  • llm_max_tokens: Maximum tokens for response (default: 10000)

Function mode

  • decision_type: Set to "function"
  • function_id: ID of the deployed Kapso Function to execute

How it works

AI-powered mode

  1. Analyzes conversation: Reviews recent WhatsApp message history
  2. Evaluates conditions: Uses AI to match user intent against condition descriptions
  3. Returns label: Chooses the best matching condition label for routing
  4. Routes workflow: Uses the label to follow the matching outgoing edge
  5. Fallback: Uses first condition if AI evaluation fails

Function mode

  1. Executes function: Calls your deployed Kapso Function with current workflow state
  2. Your logic runs: Function executes your custom logic - check data, call APIs, apply rules, etc.
  3. Returns decision: Function returns JSON with next_edge set to a condition label
  4. Routes workflow: Uses the returned label to follow the matching outgoing edge
  5. Updates variables: Optionally updates workflow variables if function returns vars
Important: Condition labels must exactly match your outgoing edge labels for proper routing in both modes.

Function decision contract

When using function mode, your function receives the standard execution payload plus an available_edges array containing all possible edge labels from your conditions.

Request payload

{
  execution_context: {
    vars: {...},           // Workflow variables
    system: {...},         // System data
    context: {...},        // Execution context
    metadata: {...}        // Metadata
  },
  flow_events: [...],      // Recent workflow events (last 10)
  flow_info: {
    id: "flow_abc123",     // Flow ID
    name: "My Flow",       // Flow name
    step_id: "step_xyz"    // Current step ID
  },
  available_edges: [       // Condition labels from your DecideNode
    "premium",
    "standard",
    "trial"
  ],
  whatsapp_context: {      // Only present when triggered by WhatsApp
    conversation: {
      id: "conv_123",
      phone_number: "+1234567890",
      status: "open",
      last_active_at: "2024-01-15T10:30:00Z",
      whatsapp_config_id: "config_abc",
      metadata: {},
      created_at: "2024-01-10T08:00:00Z",
      updated_at: "2024-01-15T10:30:00Z"
    },
    messages: [            // All messages, ordered by created_at (oldest first)
      {
        id: "msg_001",
        message_type: "text",
        content: "Hello!",
        direction: "inbound",
        status: "delivered",
        processing_status: "processed",
        whatsapp_message_id: "wamid_abc",
        origin: "user",
        phone_number: "+1234567890",
        has_media: false,
        created_at: "2024-01-15T10:00:00Z",
        updated_at: "2024-01-15T10:00:00Z"
      }
    ]
  }
}

Response format

Your function must return JSON with next_edge set to one of the labels from available_edges:
{
  next_edge: "premium",    // REQUIRED: Must match a condition label
  vars: {                  // OPTIONAL: Updated workflow variables
    customer_tier: "premium",
    discount_applied: true
  }
}

Example function

async function handler(request, env) {
  const body = await request.json();
  const availableEdges = body?.available_edges || [];
  const executionContext = body?.execution_context || {};
  const vars = executionContext.vars || {};
  const context = executionContext.context || {};

  // Your custom routing logic - can be anything:

  // 1. Business rules
  const accountAge = vars.account_age_days || 0;
  const totalSpent = vars.lifetime_value || 0;

  // 2. External API check (optional)
  // const res = await fetch(`https://api.example.com/users/${context.phone_number}/tier`);
  // const userTier = await res.json();

  // 3. Time-based logic
  // const hour = new Date().getHours();
  // if (hour < 9 || hour > 17) nextEdge = "closed";

  let nextEdge = availableEdges[0] || "default";

  if (totalSpent > 1000 && accountAge > 365) {
    nextEdge = "premium";
  } else if (accountAge > 30) {
    nextEdge = "standard";
  } else {
    nextEdge = "trial";
  }

  return new Response(JSON.stringify({
    next_edge: nextEdge,
    vars: {
      customer_tier: nextEdge,
      routing_timestamp: new Date().toISOString()
    }
  }), {
    headers: { "Content-Type": "application/json" }
  });
}

Accessing WhatsApp data

When your flow is triggered by a WhatsApp message, use whatsapp_context to access conversation data:
async function handler(request, env) {
  const body = await request.json();
  const availableEdges = body?.available_edges || [];
  const whatsappContext = body?.whatsapp_context;

  // Guard: ensure WhatsApp context exists
  if (!whatsappContext) {
    return new Response(JSON.stringify({
      next_edge: availableEdges[0]
    }), { headers: { "Content-Type": "application/json" } });
  }

  const { conversation, messages } = whatsappContext;

  // Get user's phone number
  const userPhone = conversation.phone_number;

  // Get the last message
  const lastMessage = messages[messages.length - 1];
  const lastContent = lastMessage?.content || "";
  const lastDirection = lastMessage?.direction; // "inbound" or "outbound"

  // Count inbound messages
  const userMessages = messages.filter(m => m.direction === "inbound");

  // Check for media
  const hasMedia = messages.some(m => m.has_media);

  // Access interactive reply data
  const lastReplyId = lastMessage?.message_type_data?.reply_option_id;

  // Route based on conversation state
  let nextEdge = "returning_user";
  if (userMessages.length < 2) {
    nextEdge = "new_user";
  }

  return new Response(JSON.stringify({
    next_edge: nextEdge,
    vars: { user_phone: userPhone }
  }), { headers: { "Content-Type": "application/json" } });
}
Message types: text, image, video, document, audio, location, interactive, template, reaction, contacts Direction values: inbound (from user), outbound (from bot)

Usage patterns

Question → Wait → Decide → Route Interactive → Wait → Decide