Triggers
Run a published workflow automatically — on a schedule, in response to an HTTP call, or when a business event occurs.
In addition to the Run panel and the manual POST /api/v1/agent-workflows/{id}/run API, a workflow fires based on a cron schedule, an incoming webhook, or a business event from the Event Bus. Cron and webhook triggers are configured from the Triggers panel in the workflow editor (the bolt icon). Event subscriptions are configured via API (UI tab coming soon).
All trigger types refuse to fire a workflow that isn't in published status. Stage and publish the workflow first.
Cron triggers
Run a workflow on a recurring schedule using standard 5-field cron syntax.
Creating a cron trigger
- Open the workflow in the editor and click the Triggers button.
- Click + Cron Schedule.
- Fill in:
| Field | Description |
|---|---|
| Name | Human-readable label (e.g. "Daily 9am summary") |
| Cron Expression | Standard 5-field cron syntax: minute hour day-of-month month day-of-week |
| Timezone | IANA timezone name (default UTC) |
| Input Template | Text passed as the workflow's input on each fire. Supports {date} and {now} placeholders |
Example expressions:
| Expression | Meaning |
|---|---|
0 9 * * 1-5 | 9:00 AM, Monday–Friday |
*/15 * * * * | Every 15 minutes |
0 0 1 * * | Midnight on the 1st of every month |
Example input template:
Generate the daily summary for {date}. Current time: {now}.
If the input template contains an unrecognized placeholder (e.g. {user}) or invalid syntax, the template is used verbatim — the cron trigger still fires, but the unresolved placeholder appears as literal text in the workflow input. A warning is logged for visibility.
How firing works
A background scheduler checks for due triggers every 60 seconds. When a cron trigger's next_run_at has passed:
- The trigger is atomically claimed (its schedule is advanced immediately, so it can't be double-fired if multiple app instances are running).
- The workflow runs with the rendered input template.
last_run_atandrun_countare recorded once the run completes.
No external scheduler (cron job, Celery beat, systemd timer) is required — firing happens automatically inside the app.
Manual tick (admin/debugging)
POST /api/v1/triggers/cron/tick
Authorization: Bearer <admin-token>Admins can call this directly to fire all currently-due cron triggers on demand, without waiting for the next automatic tick — useful for testing a new cron trigger immediately after creating it.
Webhook triggers
Expose a public URL that fires the workflow whenever it receives an HTTP POST.
Creating a webhook trigger
- Open the Triggers panel and click + Webhook.
- Give it a name and optionally restrict it to specific source IPs.
- Click Create — the response shows:
- Webhook URL — the public endpoint to POST to
- Signing Secret — shown once. Store it immediately; it cannot be retrieved again.
The signing secret is only ever shown at creation time. If you lose it, delete the trigger and create a new one.
Firing the webhook
POST /api/v1/triggers/webhook/{token}
Content-Type: application/json
X-Hrida-Signature: sha256=<hmac_hex>
X-Hrida-Timestamp: <unix_seconds>
{
"input": "New support ticket: customer reports a billing discrepancy"
}- The request body's
input(ormessage) field becomes the workflow's input. If the body isn't valid JSON, the raw body is used as the input text. - Returns
202 Acceptedimmediately — the workflow runs asynchronously. - If
allowed_ipswas configured, requests from other source IPs are rejected with403. - A malformed or incorrect
X-Hrida-Signatureis rejected with401.
Two signing schemes are accepted:
- Timestamped (recommended) — also send
X-Hrida-Timestamp: <unix_seconds>and signf"{timestamp}.".encode() + bodyinstead of the bare body. A request whose timestamp is more thanWEBHOOK_TIMESTAMP_TOLERANCE_SECONDS(default 300s) from the server's clock is rejected with401— so a captured, valid(body, signature)pair can't be replayed indefinitely by whoever intercepts it. - Legacy (bare body, no timestamp) — still accepted for callers configured before this scheme existed (
hmac.new(secret, body, sha256)), but has no replay protection: an intercepted request stays valid forever. Migrate to the timestamped scheme when you can.
If you send X-Hrida-Timestamp, the signature must be computed over the timestamp-prefixed payload — a legacy (bare-body) signature will not validate once a timestamp header is present.
Response
{
"accepted": true,
"trigger_id": "...",
"workflow_id": "...",
"run_id": "run_abc123"
}The run_id is created synchronously before the workflow is queued. Use it to poll GET /api/v1/agent-analytics/workflows/{workflow_id}/runs for the run's status and output — no need to scan the full run list.
This confirms the request was accepted and queued — not that the workflow succeeded.
Incident triggers
Fire a workflow automatically when a configured incident provider (e.g. Grafana OnCall) reports a state change — an alert firing, being acknowledged, or resolved. Unlike cron and webhook triggers, an incident trigger isn't a separate trigger object — it's a single setting stored directly on the workflow (graph.meta.oncall_trigger), saved immediately when you change it.
Incident triggers only fire if at least one incident provider is set up in Admin Panel → Incidents → Providers. See Grafana OnCall Provider for the full setup guide.
Setting an incident trigger
- Open the workflow in the editor and click the Triggers button.
- Under Incident Trigger, choose which state change should fire the workflow:
| Option | Fires when |
|---|---|
| Disabled | Never (default) |
| On Firing | An incident enters the firing state |
| On Acknowledged | An incident is acknowledged |
| On Resolved | An incident is resolved |
| Any state change | Any of the above |
The selection saves automatically — there's no separate "Create" step like cron or webhook triggers, and it doesn't appear in the trigger list described under Managing triggers below since it isn't a WorkflowTrigger row.
How firing works
- The incident provider POSTs a webhook to
/api/v1/webhooks/incidents/{provider_id}(configured on the provider side — see the provider's own setup doc). - hrida-ai-studio normalizes the payload into an internal event and deduplicates retries — the same
provider + incident + statecombination within 60 seconds is ignored. - Every published workflow whose Incident Trigger setting matches the event's state (or is set to Any state change) fires — a single incident event can trigger more than one workflow.
Reading incident data inside the workflow
The fired workflow's input is the incident event serialized as JSON:
{
"provider_id": "...",
"provider_type": "grafana_oncall",
"incident_id": "...",
"state": "firing",
"title": "High CPU on web-3",
"severity": "critical",
"created_at": "2026-07-17T10:15:00Z",
"acknowledged_at": null,
"resolved_at": null,
"permalink": "https://..."
}This arrives as the workflow's plain-text input, not under a state._event key the way event subscription payloads do. The sandboxed expression evaluator used by transform/set_state/if_else nodes does not expose a JSON parser, so you can't do json.loads(input) in a node expression. Use an agent node and ask the LLM to read the relevant fields out of the JSON text instead.
Managing triggers
The Triggers panel lists every cron and webhook trigger for the current workflow:
| Action | Effect |
|---|---|
| Toggle enabled | Pause/resume firing without deleting the trigger |
| Delete | Permanently remove the trigger. Webhook URLs and cron schedules stop working immediately |
Both creating and deleting webhook triggers requires agent_developer role or higher within the workflow's space; deleting requires lifecycle_manager or higher.
Creating, enabling/disabling, updating, or deleting any trigger is recorded in the parent workflow's Audit Log as an UPDATE entry — including who made the change and, for edits, a before/after diff. Webhook signing secrets are always redacted from the audit record.
Catalog and space scoping
Both trigger types accept an optional catalog_id and space_id at creation. When set, the fired run uses that catalog/space's credentials and access policies. When omitted, the run falls back to the instance's global LLM credentials.
Event subscription triggers
Fire a workflow whenever a specific business event arrives on the Event Bus. Unlike webhooks (one sender, one receiver) event subscriptions support fan-out — multiple workflows can subscribe to the same event type and all fire independently.
Event subscription triggers are currently configured via the API. A dedicated Event tab will be added to the Triggers panel in a future release.
Creating an event subscription
curl -X POST "http://localhost:8080/api/v1/agent-workflows/{workflow_id}/triggers/event" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "On leave_applied",
"event_type": "leave_applied",
"filter_expr": "payload.get(\"days\", 0) > 5",
"concurrency": "one_per_correlation"
}'| Field | Description |
|---|---|
event_type | Event type string to subscribe to (e.g. leave_applied, invoice_received) |
filter_expr | Optional Python expression evaluated against the event. Variables: payload (dict), event (full event). Return True to fire, False to skip. |
concurrency | parallel (default) — fire for every matching event. one_per_correlation — skip if a run is already active for the same correlation_id. |
Reading event data inside the workflow
When fired by an event subscription the initial workflow state contains:
{
"_event": {
"id": "evt_abc123",
"type": "leave_applied",
"payload": { "employee_id": "EMP001", "days": 5 },
"correlation_id": "EMP001",
"source": "hridaone"
}
}The workflow input (passed to the first agent node) is the event serialized as JSON. Agent nodes can read state._event.payload directly.
How events are published
External systems and internal workflows both publish to the same Event Bus:
# External system (API key, no JWT)
POST /api/v1/events/ingest
X-Api-Key: <HRIDA_EVENT_API_KEY>
# Admin / internal (JWT required)
POST /api/v1/events
Authorization: Bearer <admin_token>
# From inside a workflow — use the emit_event built-in toolSee Event-Driven Workflows for the full event model and Saga Orchestration for coordinating multi-step processes.
Trigger comparison
| Cron | Webhook | Incident | Event Subscription | |
|---|---|---|---|---|
| Trigger source | Time schedule | External HTTP POST | Incident provider state change | Business event on Event Bus |
| Configured in | Triggers panel (UI) | Triggers panel (UI) | Triggers panel (UI) | API (UI tab coming soon) |
| Stored as | WorkflowTrigger row | WorkflowTrigger row | graph.meta.oncall_trigger on the workflow | WorkflowTrigger row |
| Fan-out | No | No | Yes — one incident can fire multiple matching workflows | Yes — multiple workflows per event |
| Payload | Input template with {date} / {now} | Request body | Incident event as JSON text | Event payload + full event context |
| Auth | n/a | HMAC-SHA256 signature | n/a (provider webhook auth handled per-provider) | API key (external) or JWT (admin) |
| Saga support | No | No | No | Yes — correlation_id links events |
| Filter | n/a | IP allowlist | Incident state (firing/acknowledged/resolved/any) | Python expression on payload |
Related
- Grafana OnCall Provider — set up an incident provider so incident triggers have something to fire on
- Node Types →
sub_workflow— invoke another workflow from inside a graph, wired at build time - Built-in Tools →
trigger_workflow— let an AI agent decide at runtime to invoke another workflow - Built-in Tools →
emit_event— publish events from inside a workflow - Event-Driven Workflows — Event Bus, subscriptions, external ingestion
- Saga Orchestration — multi-step coordination with compensation
- Run Infrastructure — SSE streaming, run history, manual run API
- Traceability & Audit Log — audit trail for trigger changes, plus per-run execution tracing