Triggers
Decide when your agent picks up the task: run it by hand, on a schedule, from a signed webhook, or on an event from GitHub, Slack, or Plain.
Triggers
A trigger decides when a workflow runs. A workflow can have any number of triggers, of any mix of types, each with its own label. Whichever one fires, the agent receives your prompt plus that trigger's payload as context.
There are four types: manual, cron, webhook, and integration events.
Manual
Run the workflow yourself, on demand. Every workflow can be dispatched manually regardless of which other triggers it has, which makes this the easiest way to test a prompt.
A manual run can carry an arbitrary JSON input payload. The agent sees it as trigger context, so you can rehearse an event-driven prompt by handing it a sample event.
Cron
Run the workflow on a schedule, using a standard cron expression with either five fields
(minute, hour, day-of-month, month, day-of-week) or six (a leading seconds field). Ranges (-),
lists (,), steps (/), and wildcards (*) all work.
0 9 * * 1-5 # 9:00 every weekday
*/15 * * * * # every 15 minutes
0 0 1 * * # midnight on the 1st of each monthCron schedules run in UTC. Convert your local time before entering an expression, or describe the schedule in plain English and let Helios do it (below).
Natural language schedules
Instead of a cron expression you can describe the schedule — "every weekday at 9am Eastern", "the first Monday of the month" — and Helios converts it to a cron expression, handling the timezone conversion to UTC for you. You'll see the generated expression before you save it, and a plain-English readback of what it means.
If a description is too ambiguous to convert, Helios says so rather than guessing.
Default input
A cron trigger can carry a default JSON input passed to the agent on every scheduled run.
Webhook
A webhook trigger exposes a public URL that any HTTP client can POST to:
https://<helios-host>/webhooks/agent-trigger/<workflowId>/<triggerId>The request body is parsed as JSON and handed to the agent as trigger context. An empty body is
treated as {}. A successful request returns the id of the run it started.
Signing
Because the URL is public, every request must carry a valid HMAC-SHA256 signature derived from a per-trigger signing secret. Helios generates the secret when you add the trigger; you can reveal it from the workflow's trigger panel.
| Header | Value |
|---|---|
X-Helios-Timestamp | Unix seconds at the moment of signing |
X-Helios-Signature | sha256=<hex digest> |
To sign a request:
- Capture the raw request body as bytes.
- Compute
HMAC-SHA256(secret, "<timestamp>.<rawBody>")and hex-encode it. - Send both headers above.
import crypto from "node:crypto";
const url = "<your webhook url>";
const secret = "<your signing secret>";
const ts = Math.floor(Date.now() / 1000);
const body = JSON.stringify({ hello: "world" });
const sig = crypto
.createHmac("sha256", secret)
.update(`${ts}.${body}`)
.digest("hex");
await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Helios-Timestamp": String(ts),
"X-Helios-Signature": `sha256=${sig}`,
},
body,
});ts=$(date +%s)
body='{"hello":"world"}'
sig=$(printf '%s.%s' "$ts" "$body" \
| openssl dgst -sha256 -hmac "$SECRET" -hex \
| awk '{print $2}')
curl -X POST "$URL" \
-H "X-Helios-Timestamp: $ts" \
-H "X-Helios-Signature: sha256=$sig" \
-H 'Content-Type: application/json' \
--data "$body"Helios rejects a request with 401 when the signature header is missing or doesn't begin with
sha256=, when the timestamp is more than 5 minutes away from server time, or when the digest
doesn't match. The response body is deliberately generic; the precise reason is in your workflow's
server-side logs.
Three details that cause most signing bugs:
- The secret is signed as a UTF-8 string, not decoded from hex. Don't
Buffer.from(secret, "hex")on one side only. - The body is signed exactly as transmitted. A proxy that reformats JSON or changes whitespace will break the signature.
- An empty body signs as the empty string — sign over
"<timestamp>."with nothing after the dot.
Rotating the secret
Rotate a webhook trigger's signing secret from the workflow's trigger panel. Rotation takes effect
immediately and there is no overlap window — every existing sender gets 401 until it is
reconfigured with the new secret.
Integration events
Run the workflow when something happens in a connected service. Three services emit events into Helios today: GitHub, Slack, and Plain.
Choose the integration, the event, and any filters. The agent receives the full event payload as trigger context.
Filters
Each event exposes a set of filter fields. Within one field, multiple values are OR'd; across different fields they are AND'd. Leaving a field empty means "match anything".
Fields marked as patterns accept glob syntax: * (any characters within a segment), ? (one
character), ** (across segments), [abc] / [a-z] (character classes), and {a,b} (alternation).
GitHub
Every GitHub event requires a repository filter.
| Event | Filters |
|---|---|
pull_request.opened | repository |
pull_request.closed | repository |
pull_request.synchronize | repository |
pull_request.labeled | repository, label (pattern) |
issues.opened | repository |
issues.closed | repository |
issues.labeled | repository, label (pattern) |
push | repository, branch (pattern, required) |
tag.created | repository, tag name (pattern) |
release.published | repository |
workflow_run.completed | repository, workflow name (pattern), conclusion |
issue_comment.created | repository |
Slack
| Event | Filters |
|---|---|
message.channel — a message in a public channel | channel |
message.group — a message in a private channel | channel |
Omit the channel filter to match every channel the Helios bot can see.
Plain
| Event | Filters |
|---|---|
thread.thread_created — a customer opens a new thread | none |
thread.chat_received — a new chat message from a customer | none |
Plain requires manual webhook configuration. After connecting the integration, paste the webhook URL Helios shows you into Plain's settings so events reach Helios.
Guarding against events you don't want
Filters narrow which events reach your agent, but they can't express everything. The agent sees the event payload and your prompt together, so the prompt is the second filter — say plainly what should happen when the event doesn't warrant action:
When a pull request is opened in acme/api, review the diff.
If it only touches files under docs/, do nothing and stop.Agents are instructed to stop as soon as they determine no action is required, so a run that decides to do nothing still succeeds — it just has no side effects.
Next
- Runs and outputs — what happens after a trigger fires.
- Integrations — connecting GitHub, Slack, and Plain.
Last updated on