Triggers, scheduling, and enterprise integration patterns
The course has so far started every workflow by hand. This final lesson covers the other ways a workflow starts, the three task types the static course did not exercise directly (REST_CONNECTOR, HAYSTACK, MQTT_PUBLISH), and a reference mapping of Twinit tasks onto standard enterprise integration patterns.
Triggers#
A workflow definition is inert until something starts it. There are three triggers.
Manual Run#
A direct API call: from an app, a page-component action, or a workbench. This is what runWeatherWorkflow and what the 'Run Workflow' command does throughout the course.
POST /workflowsvc/api/v1/workflows{ "_workflowDefId": "…", "_inputParams": { "_urn": "urn:…" }}Scheduled#
Attach one or more Schedules to a WorkflowDef. Schedules are sub-resources managed through their own REST endpoints, using a cron expression:
{ "_schedule": { "_type": "TIME_BASED", "_runInterval": "0 0/5 * ? * *", "_timeZone": "UTC" }}The example above runs the workflow every five minutes. This is how you turn any of the course workflows into an unattended job (e.g. poll a weather API every 15 minutes and store readings).
You can create Workflow Schedule using a user interface int he IDE Extension.
- Expand the Workflow Definition in the Workflow Service to show the Schedules
- Right click the Schedules node and select 'Create Schedule'

- A web form will open with controls to make screaitng a schedule easy

Event-Driven#
Configure a Notification Service trigger that fires a custom script when a matching event is emitted anywhere in Twinit; the script calls IafWorkflowSvc.runWorkflow.
filter: { event: "resource.ResourceCreated", "resource.type": "filesvc.*" } → script → runWorkflow(defId, …)This is how "when a file is uploaded, process it" style automation is built, and it is the same mechanism that would deliver the USER_INPUT pause notification to an approver.
The Three External Task Types#
These task types require live endpoints, so the course did not cover them, but their shape is worth knowing because they are how workflows reach outside Twinit.
REST_CONNECTOR#
The production counterpart of the course's generate...WeatherData scripts. It supports NoAuth, Basic, Bearer, and OAuth2, client-credentials, so most enterprise APIs need no custom auth code. Its result is exposed as ._output.result (or .body) for downstream binding.
{ _name: "fetch", _type: "REST_CONNECTOR", _inputParams: { _url: "${workflow.input._url}", _auth: { _type: "OAuth2ClientCreds", /* ... */ }, _args: { method: "GET", headers: { Accept: "application/json" } }, to: "json" }}To convert any course workflow to live data, replace a generate...WeatherData SCRIPT_EXECUTION task with a REST_CONNECTOR task and update the downstream binding from ._output.scriptOutput to ._output.result. Everything else is unchanged.
It's also worth noting that fetch() can be used directly in a SCRIPT_EXECUTION task as well. Depending on your use case, fetch() may be a more flexible implementation than the REST_CONNECTOR task.
HAYSTACK#
A first-class task for SkySpark, Niagara, and other Haystack-compliant BMS endpoints. It supports actions such as pointhistory:
{ _name: "fetch_ahu_history", _type: "HAYSTACK", _sequenceno: 1, _inputParams: { _project: "site-A", _url: "https://skyspark.example.com/api", _uname: "twinit", _pwd: "${secret:skyspark_pwd}", _action: "pointhistory", _cmd: "point and his and equip->ahu", _hisStartDate: "${workflow.input._since}", _to: "json" }}Combine it with FORK_JOIN to fetch history for several point groups at once, then a SCRIPT_EXECUTION to normalise each history frame into telemetry readings (_ts + _tsMetadata._sourceId).
MQTT_PUBLISH#
Publishes a message to any MQTT broker (ActiveMQ, HiveMQ, AWS IoT Core, or a BACnet-MQTT gateway). Used for command messages to devices:
{ _name: "publish_setpoint", _type: "MQTT_PUBLISH", _inputParams: { _mqtt_target: [ { _config: { _url: "ssl://gw.example.com:8883", _username: "twinit-bms", _password: "${secret:bldg_a_pwd}", _mqtt_version: 5 } } ], _topic: "bldg/A/ahu/03/setpoint", _messages: [ "{ \"objectId\": \"analog-value:12\", \"value\": ${workflow.input.setpointC}, \"correlationId\": \"${workflow._id}\" }" ] }}Note correlationId: "${workflow._id}": the run id correlates the command with the device's echoed state on a separate topic, which flows back through Twinit's IoT ingest pipeline.
Task-to-Pattern Reference#
Twinit's built-in tasks map directly onto the most-used enterprise integration patterns. This table is the quick reference for choosing a task:
| Pattern | Twinit implementation | Course lesson |
|---|---|---|
| Router | SWITCH on ${prev._output.field} | 06 |
| Recipient List | FORK_JOIN of REST_CONNECTORs | 05 |
| Splitter + Aggregator | SCRIPT_EXECUTION (fan-out) → FORK_JOIN (join) | 07 |
| Filter | SWITCH with a drop branch (or in-script decision) | 06, 08 |
| Message Translator | SCRIPT_EXECUTION (map schema A → B) | 09 |
| Enricher | REST_CONNECTOR → SCRIPT_EXECUTION (merge) | 07 |
| Poll | DO_WHILE around REST_CONNECTOR | 08 |
| Command Message | MQTT_PUBLISH with correlationId | 13 |
| Request–Reply / Human Task | REST_CONNECTOR + USER_INPUT | 10 |
| Process Manager | the workflow itself (25-task envelope) | all |
| AI-backed Translator / Router | AI_CONVERSE | 11 |