Logic Step

Route your workflow down different paths based on conditions you define.

The Logic step lets you branch your workflow based on conditions. You define a list of branches, each with its own condition. When the session reaches the Logic step, branches are evaluated in order—the first one whose condition is true determines which path the workflow takes next. If none match, the fallback branch is taken.

Branches

A Logic step contains an ordered list of branches. Each branch has a condition that is evaluated when the session reaches this step. Every branch must have a unique label.

nametyperequireddescription
labelstringyesA unique name for the branch (e.g. "High Value Customer").
targetstringnoThe step to route to when this branch matches (description or id).
conditionobjectyesThe rule that must be true for this branch to be taken. See Conditions below.

Fallback branch

A catch-all branch is identified by label exactly Fallback branch. It is skipped during condition evaluation and used only when no other branch matches. Its condition must be an AND group with an empty conditions array:

{
  "label": "Fallback branch",
  "target": "default_step",
  "condition": {
    "type": "group",
    "logicOperator": "AND",
    "conditions": []
  }
}

Conditions

A condition can take one of three forms: a comparison between two values, an emptiness check on a single value, or a logical group that combines multiple conditions. All three forms are identified by a type field.

Persisted config uses two group levels: an outer AND or OR group whose inner groups use the opposite operator. Inner groups contain only comparisons and emptiness checks.

Comparison

A comparison condition evaluates a field from a previous step against a static value (or a list of static values) using one of the available operators.

nametyperequireddescription
typestringyesMust be binaryRule.
binaryOperatorstringyesThe comparison to apply. See Operators.
leftOperandstringyesA mapping placeholder referencing a field from a previous step (e.g. {{Webhook.response.statusCode}}).
rightOperandstring | arrayyesA static value (e.g. "200") or an array of strings for set equality / contains.
⚠️

The right operand must be a static value or an array of static strings. Comparing two mapping placeholders against each other is not currently supported.

Emptiness check

An emptiness check tests whether a single value is present or absent. The operand is typically a mapping placeholder. null and missing values are treated as empty.

nametyperequireddescription
typestringyesMust be unaryRule.
unaryOperatorstringyesIS_EMPTY or IS_NOT_EMPTY.
operandstringyesThe value to check.

Grouped conditions

A group combines multiple conditions using AND or OR logic.

nametyperequireddescription
typestringyesMust be group.
logicOperatorstringyesAND (all must be true) or OR (at least one must be true).
conditionsarrayyesNested conditions. Empty only on the fallback branch.

Operators

Comparison operators

operatorapplies todescription
EQUALnumeric, string, date, time, setLeft equals right. Numbers compare numerically ("2.0" equals "2"). Sets compare as order-independent exact matches. Datetimes compare by UTC instant.
NOT_EQUALnumeric, string, date, time, setLeft does not equal right.
GREATER_THANnumeric, date, time, datetimeLeft is greater than right. Missing operands evaluate to false.
LESS_THANnumeric, date, time, datetimeLeft is less than right.
GREATER_THAN_OR_EQUALnumeric, date, time, datetimeLeft is greater than or equal to right.
LESS_THAN_OR_EQUALnumeric, date, time, datetimeLeft is less than or equal to right.
CONTAINSstring, setString: left contains substring right. Set: left contains every value in right (extras on the left are allowed).
NOT_CONTAINSstring, setInverse of CONTAINS.
STARTS_WITHstringLeft starts with right.
DOES_NOT_START_WITHstringLeft does not start with right.
ENDS_WITHstringLeft ends with right.
DOES_NOT_END_WITHstringLeft does not end with right.
IS_LONGER_THANstring vs numeric lengthleft.length > Number(right). Right is an integer length, not another string.
IS_NOT_LONGER_THANstring vs numeric lengthleft.length <= Number(right).
IS_SHORTER_THANstring vs numeric lengthleft.length < Number(right).
IS_NOT_SHORTER_THANstring vs numeric lengthleft.length >= Number(right).
IS_LONGER_OR_EQUALstring vs numeric lengthleft.length >= Number(right).
IS_NOT_LONGER_OR_EQUALstring vs numeric lengthleft.length < Number(right).
IS_SHORTER_OR_EQUALstring vs numeric lengthleft.length <= Number(right).
IS_NOT_SHORTER_OR_EQUALstring vs numeric lengthleft.length > Number(right).

A few things to note:

  • String operators (CONTAINS, STARTS_WITH, ENDS_WITH, and their negations) operate on the string form of the operand.
  • Ordering operators (GREATER_THAN and family) require comparable operands: numeric, civil date YYYY-MM-DD, civil time HH:mm:ss, or datetime. Non-comparable values fail evaluation.
  • Length operators compare the character count of the left operand to a numeric right operand. They do not compare left.length to right.length. Example: left "hello" and right "5"IS_LONGER_OR_EQUAL is true.
  • When rightOperand is an array, EQUAL / NOT_EQUAL / CONTAINS / NOT_CONTAINS use set semantics. A scalar is treated as a one-element set ("A" equals ["A"]). Other operators join the array with ", " and fall back to string comparison.

Emptiness operators

operatordescription
IS_EMPTYOperand is null, undefined, or an empty string.
IS_NOT_EMPTYOperand is not null, undefined, or an empty string.

Examples

Single condition

Let's look at the simplest possible Logic step—one branch that checks whether a user's status equals "active".

{
  "branches": [
    {
      "label": "Active user",
      "condition": {
        "type": "group",
        "logicOperator": "AND",
        "conditions": [
          {
            "type": "group",
            "logicOperator": "OR",
            "conditions": [
              {
                "type": "binaryRule",
                "binaryOperator": "EQUAL",
                "leftOperand": "{{`User`.`status`}}",
                "rightOperand": "active"
              }
            ]
          }
        ]
      }
    }
  ]
}

Checking for an empty value

Here's a branch that checks whether an email field was left blank before continuing.

{
  "branches": [
    {
      "label": "Check if email is empty",
      "condition": {
        "type": "group",
        "logicOperator": "AND",
        "conditions": [
          {
            "type": "group",
            "logicOperator": "OR",
            "conditions": [
              {
                "type": "unaryRule",
                "unaryOperator": "IS_EMPTY",
                "operand": "{{`User`.`email`}}"
              }
            ]
          }
        ]
      }
    }
  ]
}

Set membership (rightOperand array)

Use an array rightOperand with EQUAL (exact set) or CONTAINS (subset).

{
  "branches": [
    {
      "label": "Selected both required tags",
      "target": "tagged_flow",
      "condition": {
        "type": "group",
        "logicOperator": "AND",
        "conditions": [
          {
            "type": "group",
            "logicOperator": "OR",
            "conditions": [
              {
                "type": "binaryRule",
                "binaryOperator": "CONTAINS",
                "leftOperand": "{{`Form`.`tags`}}",
                "rightOperand": ["vip", "priority"]
              }
            ]
          }
        ]
      }
    }
  ]
}

Length and date comparison

IS_LONGER_THAN tests character count against a number. GREATER_THAN on dates uses civil-date / datetime ordering.

{
  "branches": [
    {
      "label": "Long note after cutoff",
      "target": "review_flow",
      "condition": {
        "type": "group",
        "logicOperator": "AND",
        "conditions": [
          {
            "type": "group",
            "logicOperator": "OR",
            "conditions": [
              {
                "type": "binaryRule",
                "binaryOperator": "IS_LONGER_THAN",
                "leftOperand": "{{`Form`.`notes`}}",
                "rightOperand": "100"
              }
            ]
          },
          {
            "type": "group",
            "logicOperator": "OR",
            "conditions": [
              {
                "type": "binaryRule",
                "binaryOperator": "GREATER_THAN",
                "leftOperand": "{{`Form`.`submittedOn`}}",
                "rightOperand": "2026-01-01"
              }
            ]
          }
        ]
      }
    }
  ]
}

Multiple branches with nested logic

Building on those basics, here's a complete example with three branches. Each branch uses a different combination of operators and nested groups to route customers to the right flow.

{
  "branches": [
    {
      "label": "High-value active customer",
      "target": "premium_flow",
      "condition": {
        "type": "group",
        "logicOperator": "AND",
        "conditions": [
          {
            "type": "group",
            "logicOperator": "OR",
            "conditions": [
              {
                "type": "binaryRule",
                "binaryOperator": "EQUAL",
                "leftOperand": "{{`User`.`status`}}",
                "rightOperand": "active"
              }
            ]
          },
          {
            "type": "group",
            "logicOperator": "OR",
            "conditions": [
              {
                "type": "binaryRule",
                "binaryOperator": "GREATER_THAN_OR_EQUAL",
                "leftOperand": "{{`User`.`credit`}}",
                "rightOperand": "1000"
              }
            ]
          },
          {
            "type": "group",
            "logicOperator": "OR",
            "conditions": [
              {
                "type": "binaryRule",
                "binaryOperator": "CONTAINS",
                "leftOperand": "{{`User`.`kind`}}",
                "rightOperand": "vip"
              },
              {
                "type": "binaryRule",
                "binaryOperator": "GREATER_THAN",
                "leftOperand": "{{`User`.`credit`}}",
                "rightOperand": "10000"
              }
            ]
          }
        ]
      }
    },
    {
      "label": "New customer with valid email",
      "target": "onboarding_flow",
      "condition": {
        "type": "group",
        "logicOperator": "AND",
        "conditions": [
          {
            "type": "group",
            "logicOperator": "OR",
            "conditions": [
              {
                "type": "binaryRule",
                "binaryOperator": "EQUAL",
                "leftOperand": "{{`Customer`.`kind`}}",
                "rightOperand": "new"
              }
            ]
          },
          {
            "type": "group",
            "logicOperator": "OR",
            "conditions": [
              {
                "type": "unaryRule",
                "unaryOperator": "IS_NOT_EMPTY",
                "operand": "{{`Customer`.`email`}}"
              }
            ]
          }
        ]
      }
    },
    {
      "label": "Fallback branch",
      "target": "standard_flow",
      "condition": {
        "type": "group",
        "logicOperator": "AND",
        "conditions": []
      }
    }
  ]
}

Did this page help you?