Skip to content
Settings

Workflow Config Guide

Understanding Cyoda JSON workflow configurations.

Cyoda workflows define finite, distributed state machines that govern the lifecycle of business entities in an event-driven environment. Each entity progresses through a sequence of states based on defined transitions, criteria, and processing rules.

The platform supports adaptable entity modeling, allowing business logic to evolve through configuration rather than implementation changes. Workflows declare the set of states, valid transitions, and associated processing steps while preserving immutable persistence for auditability.

  1. States: Lifecycle stages of an entity
  2. Transitions: Directed changes between states
  3. Criteria: Conditional logic for transition eligibility
  4. Processors: Executable logic triggered during transitions

You can find the workflow schema in the API Documentation. See the workflow import endpoints for complete schema specifications. Here we explain the structure and meaning of each element.

{
"version": "1.0",
"name": "Workflow Name",
"desc": "Workflow description",
"initialState": "StateName",
"active": true,
"criterion": {},
"states": {}
}
  • version: Workflow schema version
  • name: Identifier for the workflow
  • desc: Detailed description of the workflow
  • initialState: Starting point for new entities
  • active: Indicates whether the workflow is active
  • criterion: Optional criterion for choosing the workflow for an entity
  • states: Map of states with transition definitions

States describe lifecycle phases for entities. Names must start with a letter and use only alphanumeric characters, underscores, or hyphens.

"StateName": {
"transitions": []
}
  • Initial state: The initial state of a new entity
  • Terminal States: States with no outgoing transitions

Transitions define allowed movements between states, optionally gated by conditions and supported by executable logic.

{
"name": "TransitionName",
"next": "TargetState",
"manual": true,
"disabled": false,
"criterion": {},
"processors": []
}
  • manual: deterimines if the transition is manual or automated
  • disabled: Marks the transition as inactive
  • criterion: Optional condition for eligibility
  • processors: Optional processing steps

Transitions may be either manual or automated, and are guarded by criteria that determine their eligibility. When an entity enters a new state, the first eligible automated transition is executed immediately within the same transaction. This continues recursively until no further automated transitions are applicable, resulting in a stable state. Each transition may trigger one or more attached processes, which can run synchronously or asynchronously, either within the current transaction or in a separate one. This forms the foundation for event flow automation, where processors may create or mutate entities in response, allowing a single transition to initiate a cascade of events and function executions across the system.

Criteria define logic that determines if a transition is permitted. A criteria can be of three different types:

  1. Function: Calls a custom function for evaluation
  2. Simple: Evaluates a single condition on entity data
  3. Group: Combines multiple criteria with logical AND/OR operators
"criterion": {
"type": "function",
"function": {
"name": "FunctionName",
"config": {
"attachEntity": true,
"context": "optionalContext"
}
}
}
  • name: The name of the function to execute
  • attachEntity: Whether to pass the entity data to the function
  • context: Optional string parameter passed to the function for additional context or configuration.

The context is passed “as is” with the event to the compute node. It can contain any sort of information that is relevant to the function’s execution, in any format. The interpretation is up to the function itself.

Simple criteria evaluate a single condition directly on entity data using JSONPath expressions.

"criterion": {
"type": "simple",
"jsonPath": "$.amount",
"operatorType": "GREATER_THAN",
"value": 1000
}

See the API Documentation for the operator types.

  • jsonPath: JSONPath expression to extract the value from entity data
  • operatorType: Comparison operator (EQUALS, GREATER_THAN, LESS_THAN, CONTAINS, etc.)
  • value: The value to compare against

Simple Criteria are executed directly on the processing node, without involving external compute nodes.

Group criteria combine multiple conditions using logical operators.

"criterion": {
"type": "group",
"operator": "AND",
"conditions": [
{
"type": "simple",
"jsonPath": "$.status",
"operatorType": "EQUALS",
"value": "VALIDATED"
},
{
"type": "simple",
"jsonPath": "$.amount",
"operatorType": "GREATER_THAN",
"value": 500
}
]
}
  • operator: Logical operator combining conditions (AND, OR)
  • conditions: Array of criteria (can be simple, function, or nested group types)

Processors implement custom logic to run during transitions.

{
"name": "ProcessorName",
"executionMode": "SYNC",
"config": {
"attachEntity": true,
"calculationNodesTags": "tag1,tag2"
}
}
  • SYNC: Inline execution within the transaction
  • ASYNC_NEW_TX: Deferred execution in a separate transaction
  • ASYNC_SAME_TX: Deferred within the current transaction

Synchronous executions run immediately and block the current processing thread on the same node, making them local and non-distributed. In contrast, asynchronous executions are scheduled for deferred processing and can be handled by any node in the cluster, enabling horizontal scalability and workload distribution, albeit with possibly somewhat higher latency.

If the processor requires access to the entity data, set attachEntity to true. This is usually the case.

As described in the Architecture section, the execution of processors and criteria is delegated to client compute nodes, i.e. your own infrastructure running your business logic. These nodes can be organized into groups and tagged based on their roles or capabilities. By optionally setting the calculationNodesTags property in a processor or criterion definition, you can direct execution to specific groups, giving you fine-grained control over workload distribution across your compute environment.

This workflow models the lifecycle of a payment request, covering validation, matching, approval, and notification handling.

It starts in the INVALID state, where the request is either amended or validated. If validation succeeds and a matching order exists, the request advances automatically to the SUBMITTED state. If not, it moves to PENDING, where it awaits a matching order or may be retried manually. Requests in SUBMITTED require an approval decision, leading either to APPROVED, which triggers asynchronous processing like payment message creation and ACK notifications, or to DECLINED, which emits a rejection (NACK) notification. Manual amend and retry transitions at key stages allow users or systems to correct or re-evaluate the request.

The following section walks through the configuration step by step.

Payment Request Workflow
{
"version": "1.0",
"name": "Payment Request Workflow",
"desc": "Payment request processing workflow with validation, approval, and notification states",
"initialState": "INVALID",
"active": true
}

Start by defining the overall structure of states and transitions.

{
"version": "1.0",
"name": "Payment Request Workflow",
"desc": "Payment request processing workflow with validation, approval, and notification states",
"initialState": "INVALID",
"active": true,
"states": {
"INVALID": {
"transitions": [
{
"name": "VALIDATE",
"next": "PENDING",
"manual": false,
"disabled": false
},
{
"name": "AMEND",
"next": "INVALID",
"manual": true,
"disabled": false
},
{
"name": "CANCEL",
"next": "CANCELED",
"manual": true,
"disabled": false
}
]
},
"PENDING": {
"transitions": [
{
"name": "MATCH",
"next": "SUBMITTED",
"manual": false,
"disabled": false
},
{
"name": "RETRY",
"next": "PENDING",
"manual": true,
"disabled": false
},
{
"name": "CANCEL",
"next": "CANCELED",
"manual": true,
"disabled": false
}
]
},
"SUBMITTED": {
"transitions": [
{
"name": "APPROVE",
"next": "APPROVED",
"manual": true,
"disabled": false
},
{
"name": "DENY",
"next": "DECLINED",
"manual": true,
"disabled": false
}
]
},
"APPROVED": {
"transitions": []
},
"DECLINED": {
"transitions": []
},
"CANCELED": {
"transitions": []
}
}
}

We add criteria to the VALIDATE and MATCH transitions:

{
"version": "1.0",
"name": "Payment Request Workflow",
"desc": "Payment request processing workflow with validation, approval, and notification states",
"initialState": "INVALID",
"active": true,
"states": {
"INVALID": {
"transitions": [
{
"name": "VALIDATE",
"next": "PENDING",
"manual": false,
"disabled": false,
"criterion": {
"type": "function",
"function": {
"name": "IsValid",
"config": {
"attachEntity": true
}
}
}
},
{
"name": "AMEND",
"next": "INVALID",
"manual": true,
"disabled": false
},
{
"name": "CANCEL",
"next": "CANCELED",
"manual": true,
"disabled": false
}
]
},
"PENDING": {
"transitions": [
{
"name": "MATCH",
"next": "SUBMITTED",
"manual": false,
"disabled": false,
"criterion": {
"type": "function",
"function": {
"name": "HasOrder",
"config": {
"attachEntity": true
}
}
}
},
{
"name": "RETRY",
"next": "PENDING",
"manual": true,
"disabled": false
},
{
"name": "CANCEL",
"next": "CANCELED",
"manual": true,
"disabled": false
}
]
},
"SUBMITTED": {
"transitions": [
{
"name": "APPROVE",
"next": "APPROVED",
"manual": true,
"disabled": false
},
{
"name": "DENY",
"next": "DECLINED",
"manual": true,
"disabled": false
}
]
},
"APPROVED": {
"transitions": []
},
"DECLINED": {
"transitions": []
},
"CANCELED": {
"transitions": []
}
}
}

We add two processors to the APPROVE transition in the SUBMITTED state, respectively, to finish the job.

{
"version": "1.0",
"name": "Payment Request Workflow",
"desc": "Payment request processing workflow with validation, approval, and notification states",
"initialState": "INVALID",
"active": true,
"states": {
"INVALID": {
"transitions": [
{
"name": "VALIDATE",
"next": "PENDING",
"manual": false,
"disabled": false,
"criterion": {
"type": "function",
"function": {
"name": "IsValid",
"config": {
"attachEntity": true
}
}
}
},
{
"name": "AMEND",
"next": "INVALID",
"manual": true,
"disabled": false
},
{
"name": "CANCEL",
"next": "CANCELED",
"manual": true,
"disabled": false
}
]
},
"PENDING": {
"transitions": [
{
"name": "MATCH",
"next": "SUBMITTED",
"manual": false,
"disabled": false,
"criterion": {
"type": "function",
"function": {
"name": "HasOrder",
"config": {
"attachEntity": true
}
}
}
},
{
"name": "RETRY",
"next": "PENDING",
"manual": true,
"disabled": false
},
{
"name": "CANCEL",
"next": "CANCELED",
"manual": true,
"disabled": false
}
]
},
"SUBMITTED": {
"transitions": [
{
"name": "APPROVE",
"next": "APPROVED",
"manual": true,
"disabled": false,
"processors": [
{
"name": "Create Payment Message",
"executionMode": "ASYNC_NEW_TX",
"config": { "attachEntity": true }
},
{
"name": "Send ACK Notification",
"executionMode": "ASYNC_NEW_TX",
"config": { "attachEntity": false }
}
]
},
{
"name": "DENY",
"next": "DECLINED",
"manual": true,
"disabled": false,
"processors": [
{
"name": "Send NACK Notification",
"executionMode": "ASYNC_NEW_TX",
"config": { "attachEntity": false }
}
]
}
]
},
"APPROVED": {
"transitions": []
},
"DECLINED": {
"transitions": []
},
"CANCELED": {
"transitions": []
}
}
}
  • Use domain-specific state names
  • Match transition granularity to business needs
  • Define recovery and cancellation paths
  • Prefer asynchronous processing for external dependencies
  • Use self-transitions for triggering workflow automation on exit from the current state

Cyoda workflows integrate directly with:

  • Entity Models: Determine which workflows apply to which data types
  • Execution Engine: Drives state and transition logic
  • External Functions: Implement validation and custom behavior
  • Event System: Triggers automated transitions on event reception