trigger -> http1 -> http2 -> http3
--- workflow_events
ID WF_ID NODE_ID CHANNEL DATA STATE
ev1 wf1 trigger1 default {} pending--- workflow_events
ID WF_ID NODE_ID CHANNEL DATA STATE
ev1 wf1 trigger1 default {} routed
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT_SOURCE STATE
ex1 wf1 http1 ev1 ev1 pending--- workflow_events
ID WF_ID NODE_ID CHANNEL DATA STATE
ev1 wf1 trigger1 default {} routed
ev2 wf1 http1 default {a: 1} pending
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT_SOURCE STATE
ex1 wf1 http1 ev1 ev1 finishedThen:
--- workflow_events
ID WF_ID NODE_ID CHANNEL DATA STATE
ev1 wf1 trigger1 default {} routed
ev2 wf1 http1 default {a: 1} routed
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT_SOURCE STATE
ex1 wf1 http1 ev1 ev1 finished
ex1 wf1 http2 ev1 ev2 pendingThen:
--- workflow_events
ID WF_ID NODE_ID CHANNEL DATA STATE
ev1 wf1 trigger1 default {} routed
ev2 wf1 http1 default {a: 1} routed
ev2 wf1 http2 default {b: 2} pending
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT_SOURCE STATE
ex1 wf1 http1 ev1 ev1 finished
ex1 wf1 http2 ev1 ev2 finishedThen:
--- workflow_events
ID WF_ID NODE_ID CHANNEL DATA STATE
ev1 wf1 trigger1 default {} routed
ev2 wf1 http1 default {a: 1} routed
ev2 wf1 http2 default {b: 2} routed
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT_SOURCE STATE
ex1 wf1 http1 ev1 ev1 finished
ex1 wf1 http2 ev1 ev2 finished
ex1 wf1 http3 ev1 ev3 pendingThen:
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} routed
ev2 wf1 http1 ex1 default {a: 1} routed
ev3 wf1 http2 ex2 default {b: 2} routed
ev4 wf1 http3 ex3 default {b: 2} routed
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT PREVIOUS_EXECUTION STATE
ex1 wf1 http1 ev1 ev1 - finished
ex2 wf1 http2 ev1 ev2 ex1 finished
ex3 wf1 http3 ev1 ev3 ex2 finishedThe workflow is:
http1 -> blueprint1 -> http2
The blueprint is:
if1 -- true -> http1 --- ok --->
- false -> http2 - not_ok ->
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} pendingThen:
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} routed
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT PREVIOUS STATE
ex1 wf1 http1 ev1 ev1 - pendingThen:
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} routed
ev2 wf1 http1 ex1 default {} pending
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT PREVIOUS STATE
ex1 wf1 http1 ev1 ev1 - finishedThen:
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} routed
ev2 wf1 http1 ex1 default {} routed
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT PREVIOUS PARENT STATE
ex1 wf1 http1 ev1 ev1 - - finished
ex2 wf1 blueprint1 ev1 ev1 ex1 - pendingThen:
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} routed
ev2 wf1 http1 ex1 default {} routed
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT PREVIOUS PARENT STATE
ex1 wf1 http1 ev1 ev1 - - finished
ex2 wf1 blueprint1 ev1 ev1 ex1 - started
ex3 wf1 blueprint1:if1 ev1 ev1 ex2 ex2 pendingThen:
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} routed
ev2 wf1 http1 ex1 default {} routed
ev3 wf1 blueprint1:if1 ex3 true {} pending
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT PREVIOUS PARENT STATE
ex1 wf1 http1 ev1 ev1 - - finished
ex2 wf1 blueprint1 ev1 ev1 ex1 - started
ex3 wf1 blueprint1:if1 ev1 ev1 ex2 ex2 finishedThen:
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} routed
ev2 wf1 http1 ex1 default {} routed
ev3 wf1 blueprint1:if1 ex3 true {} routed
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT PREVIOUS PARENT STATE
ex1 wf1 http1 ev1 ev1 - - finished
ex2 wf1 blueprint1 ev1 ev2 ex1 - started
ex3 wf1 blueprint1:if1 ev1 ev1 ex2 ex2 finished
ex3 wf1 blueprint1:http1 ev1 ev3 ex3 ex2 pendingThen:
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} routed
ev2 wf1 http1 ex1 default {} routed
ev3 wf1 blueprint1:if1 ex3 true {} routed
ev4 wf1 blueprint1:http1 ex4 default {} pending
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT PREVIOUS PARENT STATE
ex1 wf1 http1 ev1 ev1 - - finished
ex2 wf1 blueprint1 ev1 ev2 ex1 - started
ex3 wf1 blueprint1:if1 ev1 ev1 ex2 ex2 finished
ex4 wf1 blueprint1:http1 ev1 ev3 ex3 ex2 finishedThen:
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} routed
ev2 wf1 http1 ex1 default {} routed
ev3 wf1 blueprint1:if1 ex3 true {} routed
ev4 wf1 blueprint1:http1 ex4 default {} routed
ev5 wf1 blueprint1 ex2 ok {} pending
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT PREVIOUS PARENT STATE
ex1 wf1 http1 ev1 ev1 - - finished
ex2 wf1 blueprint1 ev1 ev2 ex1 - finished
ex3 wf1 blueprint1:if1 ev1 ev1 ex2 ex2 finished
ex4 wf1 blueprint1:http1 ev1 ev3 ex3 ex2 finishedThen:
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} routed
ev2 wf1 http1 ex1 default {} routed
ev3 wf1 blueprint1:if1 ex3 true {} routed
ev4 wf1 blueprint1:http1 ex4 default {} routed
ev5 wf1 blueprint1 ex2 ok {} routed
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT PREVIOUS PARENT STATE
ex1 wf1 http1 ev1 ev1 - - finished
ex2 wf1 blueprint1 ev1 ev2 ex1 - finished
ex3 wf1 blueprint1:if1 ev1 ev1 ex2 ex2 finished
ex4 wf1 blueprint1:http1 ev1 ev3 ex3 ex2 finished
ex5 wf1 http2 ev1 ev5 ex2 - pendingThen:
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} routed
ev2 wf1 http1 ex1 default {} routed
ev3 wf1 blueprint1:if1 ex3 true {} routed
ev4 wf1 blueprint1:http1 ex4 default {} routed
ev5 wf1 blueprint1 ex2 ok {} routed
ev6 wf1 http2 ex5 default {} pending
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT PREVIOUS PARENT STATE
ex1 wf1 http1 ev1 ev1 - - finished
ex2 wf1 blueprint1 ev1 ev2 ex1 - finished
ex3 wf1 blueprint1:if1 ev1 ev1 ex2 ex2 finished
ex4 wf1 blueprint1:http1 ev1 ev3 ex3 ex2 finished
ex5 wf1 http2 ev1 ev5 ex2 - finishedLastly:
--- workflow_events
ID WF_ID NODE_ID EXECUTION_ID CHANNEL DATA STATE
ev1 wf1 trigger1 - default {} routed
ev2 wf1 http1 ex1 default {} routed
ev3 wf1 blueprint1:if1 ex3 true {} routed
ev4 wf1 blueprint1:http1 ex4 default {} routed
ev5 wf1 blueprint1 ex2 ok {} routed
ev6 wf1 http2 ex5 default {} routed
--- workflow_node_executions
ID WF_ID NODE_ID ROOT_EVENT INPUT PREVIOUS PARENT STATE
ex1 wf1 http1 ev1 ev1 - - finished
ex2 wf1 blueprint1 ev1 ev2 ex1 - finished
ex3 wf1 if1 ev1 ev2 ex2 ex2 finished
ex4 wf1 http1 ev1 ev3 ex3 ex2 finished
ex5 wf1 http2 ev1 ev5 ex4 - finished--- 1. Fetch execution records select * from workflow_node_executions where workflow_id = wf1 and node_id = http1;
--- 2. Fetch input records select * from workflow_events where id IN (ev1, ev2, ev3);
--- 3. Fetch output records select * from workflow_events where execution_id IN (ex1, ex2, ex3)
--- Build the API response using the data above. Not great, but not a N+1 problem anymore
GET /components GET /components/{name} GET /components/{name}/actions
GET /blueprints POST /blueprints GET /blueprints/{id} PUT /blueprints/{id} DELETE /blueprints/{id}
GET /workflows POST /workflows GET /workflows/{id} PUT /workflows/{id} DELETE /workflows/{id}
GET /workflows/{workflow_id}/nodes/{node_id}/executions - List all executions for a node POST /workflows/{workflow_id}/nodes/{node_id}/events - Manually generate an output event for a node GET /workflows/{workflow_id}/executions/{execution_id}/children - List all child executions for a parent execution POST /workflows/{workflow_id}/executions/{execution_id}/actions/{action_name} - Invoke action on an execution GET /workflows/{workflow_id}/events - List all root events for a workflow GET /workflows/{workflow_id}/events/{event_id}/executions - List all executions for a root event
#
# Single output channel
#
outputChannels:
- name: default
nodeId: noop-noop-123
channel: default
edges:
- sourceId: filter-filter-q8jpss
targetId: http-http-rt6ym1
channel: default
- sourceId: filter-filter-q8jpss
targetId: http-http-t8irns
channel: default
- sourceId: http-http-t8irns
targetId: noop-noop-123
channel: default
- sourceId: http-http-rt6ym1
targetId: noop-noop-123
channel: default
nodes:
- id: filter-filter-q8jpss
name: filter
type: TYPE_COMPONENT
configuration:
expression: true
component:
name: filter
- id: http-http-t8irns
name: http
type: TYPE_COMPONENT
configuration:
method: POST
url: https://rbaskets.in/dhqfuva
component:
name: http
- id: http-http-rt6ym1
name: http
type: TYPE_COMPONENT
configuration:
method: POST
url: https://rbaskets.in/qe69jd4
component:
name: http
- id: noop-noop-123
name: noop
type: TYPE_COMPONENT
configuration: {}
component:
name: noop
#
# Multiple output channels
#
outputChannels:
- name: up
nodeId: http-http-rt6ym1
nodeOutputChannel: default
- name: down
nodeId: http-http-t8irns
nodeOutputChannel: default
edges:
- sourceId: filter-filter-q8jpss
targetId: http-http-rt6ym1
channel: default
- sourceId: filter-filter-q8jpss
targetId: http-http-t8irns
channel: default
nodes:
- id: filter-filter-q8jpss
name: filter
type: TYPE_COMPONENT
configuration:
expression: true
component:
name: filter
- id: http-http-t8irns
name: http
type: TYPE_COMPONENT
configuration:
method: POST
url: https://rbaskets.in/dhqfuva
component:
name: http
- id: http-http-rt6ym1
name: http
type: TYPE_COMPONENT
configuration:
method: POST
url: https://rbaskets.in/qe69jd4
component:
name: httpThe difference between triggers and components is that triggers cannot be connected to.
You can emit an event for any node in the workflow with: /api/v1/workflows/{workflow_id}/nodes/{node_id}/events
Trigger nodes may also have another endpoint, which uses different authentication than the one used by the API: /api/v1/webhooks/{webhook_id}
The handler for that endpoint will find the webhook and its handlers, and invoke the actions specified on them.
How do I give the plain key back to the user? When I update the schedule configuration, the next_trigger should be updated too.
I'm thinking of going back to the Setup() method instead of the Start() one.
We also need a way to reset the secret for webhook triggers
scheduled trigger
- PUT /workflows/{id} with trigger node with configuration
- WorkflowNode record in pending state is created
- Trigger.Init() is called, populating the node metadata with next_trigger
- WorkflowNodeProvisioner picks pending workflow_node record
- WorkflowNodeProvisioner calls Trigger.Setup() - nothing to be done
- workflow_node record is updated to ready state
webhook trigger
- PUT /workflows/{id} with trigger node with configuration
- WorkflowNode record in pending state is created
- Trigger.Init() is called, populating the node metadata with webhook ID, and returning the key
- WorkflowNodeProvisioner picks pending workflow_node record
- WorkflowNodeProvisioner calls Trigger.Setup() - nothing to be done
- workflow_node record is updated to ready state
github trigger
- PUT /workflows/{id} with trigger node with configuration
- WorkflowNode record in pending state is created
- Trigger.Init() is called, populating the node metadata with webhook ID
- WorkflowNodeProvisioner picks pending workflow_node record
- WorkflowNodeProvisioner calls Trigger.Setup() - webhook is created in github using values in webhook
- workflow_node record is updated to ready state
a workflow node has none or one webhook a webhook can be shared between multiple workflow nodes each webhook has specific configuration
workflow_nodes:
WF_ID NODE_ID WEBHOOK METADATA CONFIGURATION
wf1 node1 hook1 {"repository": {...}} {"integration": "integration1", "repository": "repository1", "events": ["pull_request"]}
wf1 node2 hook2 {} {"integration": "integration1", "repository": "repository1", "events": ["push"]}
wf1 node3 hook3 {} {"integration": "integration1", "repository": "repository1"}
wf1 node4 hook3 {} {"integration": "integration1", "repository": "repository1"}
webhooks:
ID SECRET STATE CONFIGURATION INTEGRATION_ID EXTERNAL_RESOURCE_ID
webhook1 secret1 ready {"events": ["pull_request"]} integration1 repository1
webhook2 secret2 pending {"events": ["push"]} integration1 repository1
webhook3 secret3 ready {"events": ["workflow_run"]} integration1 repository1
Pending records:
records:
- index: 0
type: user
state: pending
user:
id: 7ff931d3-2ffd-4011-8103-c5aa426964e4
name: "Lucas Pinheiro"
email: lucas@superplane.com
- type: role
role: org_admin
index: 1
state: pending
- type: group
group: Development
index: 2
state: pending
Finished:
records:
- index: 0
type: user
state: approved
approval:
at: "2025-10-26T00:00:00Z"
comment: ""
user:
id: 7ff931d3-2ffd-4011-8103-c5aa426964e4
name: "Lucas Pinheiro"
email: lucas@superplane.com
- type: role
role: org_admin
index: 1
state: approved
approval:
at: "2025-10-26T00:00:00Z"
comment: ""
user:
id: 7ff931d3-2ffd-4011-8103-c5aa426964e4
name: "Igor Sarcevic"
email: igor@superplane.com
- type: group
group: Development
index: 2
state: rejected
rejection:
at: "2025-10-26T00:00:00Z"
reason: ""
user:
id: 7ff931d3-2ffd-4011-8103-c5aa426964e4
name: "Pedro"
email: pedro@superplane.com
- The OutputChannels() method of the switch component returns a list of output channels based on the configuration.
- The Actions() exposed by a component could also change depending on the configuration. Not only that, but the parameters for the actions might also change depending on the configuration.
Right now, these things are static. We use the /components/{name} and /triggers/{name} endpoint to get the configuration, output channels, and actions.
But, it seems like we should only use that endpoint for the configuration, because the configuration is always static. The output channels and actions could change depending on the configuration, so we should probably expose them differently.
Maybe in: /workflows/{workflow_id}/nodes/{node_id}/output_channels /workflows/{workflow_id}/nodes/{node_id}/actions
Or maybe that information is returned as part of the WorkflowNode information given when describing the workflow, in the /workflows/{workflow_id} endpoint.