Skip to main content

Automate a Flow with Triggers

A Trigger binds a startermanual, webhook, or schedule — to an executable target — an orchestration, agent, or tool. Every firing is recorded as an auditable firing record, and runs under a confined run-as identity derived from the trigger's creator. In this tutorial you will:

  1. Log in as admin.
  2. Create a project.
  3. Create a small orchestration to activate.
  4. Bind a manual trigger to it and fire it, then inspect the firing record.
  5. Schedule the same flow with a cron trigger.
  6. Create a webhook trigger and read its signing secret.

By the end you will understand how one trigger resource activates any target from any of the three starters.

Prerequisites

  • SOAT running locally. Follow the Quick Start guide to bring the stack up with Docker Compose.
  • New to SOAT? Read Key Concepts to understand projects, orchestrations, and the IAM model first.
  • CLI installed and configured, or SDK set up. See CLI or SDK.
  • For production hardening (secrets, env vars), see Advanced Configuration.
  • Server is at http://localhost:5047.

Export your server URL (used in subsequent steps):

export SOAT_BASE_URL=http://localhost:5047

CLI path flags in this tutorial are resource-specific and kebab-cased, for example --trigger-id and --project-id.


Step 1 — Log in as admin

Admin is the built-in superuser role. See IAM — Authentication for details on JWT tokens and the admin role.

soat login-user --username admin --password Admin1234!

The CLI prints a token. Save it to your profile:

soat configure
# Token: <paste token here>

Step 2 — Create a project

Triggers, orchestrations, and firings all live inside a Project.

PROJECT_ID=$(soat create-project --name "Triggers Tutorial" | jq -r '.id')
echo "Project: $PROJECT_ID"

Step 3 — Create an orchestration to activate

The trigger's target can be an orchestration, agent, or tool. Here we use a minimal Orchestration — a single transform node — so it runs synchronously without needing an AI provider.

ORCH_ID=$(soat create-orchestration \
--project-id "$PROJECT_ID" \
--name "daily-cycle" \
--nodes '[{"id":"seed","type":"transform","expression":{"var":"cycle"},"output_mapping":{"result":"state.cycle"}}]' \
--edges '[]' | jq -r '.id')
echo "Orchestration: $ORCH_ID"

Step 4 — Create a manual trigger

A manual Trigger is fired on demand. input is the trigger's static input, shallow-merged under each firing's runtime input. Creating a trigger requires the target-start permission (orchestrations:StartRun here) — see Triggers — Run-as Identity.

TRIGGER_ID=$(soat create-trigger \
--project-id "$PROJECT_ID" \
--name "run-daily-cycle" \
--type manual \
--target-type orchestration \
--target-id "$ORCH_ID" \
--input '{"cycle":"daily"}' | jq -r '.id')
echo "Trigger: $TRIGGER_ID"

Step 5 — Fire the trigger and inspect the firing

Firing a manual trigger runs the target synchronously and returns a terminal firing record. The fire-time input is merged over the trigger's static input. Each firing is retained for auditing.

FIRING_ID=$(soat fire-trigger --trigger-id "$TRIGGER_ID" \
--input '{"cycle":"2025-01-01"}' | jq -r '.id')
echo "Firing: $FIRING_ID"

# Inspect the terminal firing record (status is succeeded/failed).
soat get-trigger-firing --firing-id "$FIRING_ID"

# List all firings for this trigger.
soat list-trigger-firings --trigger-id "$TRIGGER_ID"

Step 6 — Schedule the same flow with cron

A schedule trigger fires on a cron cadence instead of on demand. The cron field is a strict 5-field UTC expression; the server computes next_fire_at and a background poller fires due triggers exactly once, coalescing missed occurrences. See Triggers — Schedules and Misfire Coalescing.

SCHEDULE_ID=$(soat create-trigger \
--project-id "$PROJECT_ID" \
--name "daily-cycle-8am" \
--type schedule \
--target-type orchestration \
--target-id "$ORCH_ID" \
--cron "0 8 * * *" \
--input '{"cycle":"scheduled"}' | jq -r '.id')

# next_fire_at is server-computed in UTC.
soat get-trigger --trigger-id "$SCHEDULE_ID"

The scheduler fires this trigger automatically; you do not fire it yourself.


Step 7 — Trigger from an inbound webhook

A webhook trigger is fired by an external system POSTing to a public inbound endpoint. Creating one returns a signing secret (also retrievable with get-trigger-secret and replaceable with rotate-trigger-secret). See Triggers — Inbound Webhook Endpoint. This is the inbound counterpart to outbound Webhooks.

WEBHOOK_TRIGGER_ID=$(soat create-trigger \
--project-id "$PROJECT_ID" \
--name "cycle-on-webhook" \
--type webhook \
--target-type orchestration \
--target-id "$ORCH_ID" | jq -r '.id')

# Retrieve the current signing secret (webhook triggers only).
soat get-trigger-secret --trigger-id "$WEBHOOK_TRIGGER_ID"

# Rotate it when it may have leaked.
soat rotate-trigger-secret --trigger-id "$WEBHOOK_TRIGGER_ID"

The inbound POST /hooks/triggers/{trigger_id} call is made by the external system, not the SOAT CLI — it carries no bearer token and is authenticated solely by the X-Soat-Signature HMAC header over the raw request body.


Recap

You bound one orchestration to three different starters:

  • a manual trigger you fired on demand and audited via its firing record;
  • a schedule trigger the built-in poller fires on a cron cadence;
  • a webhook trigger an external system fires with an HMAC-signed inbound request.

The same pattern works with target_type: agent and target_type: tool. To ship a trigger alongside the resource it activates as a single deployable stack, declare it as a trigger resource in a Formation template — see Triggers — Formation Support.