Webhooks, Schedules & Tasks
The Hutly CLI manages three event-driven automation surfaces:
- Webhooks — standalone inbound URLs that fire handlers (a workflow or an agent) when called.
- Schedules — cron triggers that fire handlers on a recurring UTC schedule.
- Manual tasks — the human-in-the-loop approval tasks emitted by workflow approval nodes.
Every command resolves the target organization from your CLI config and key, and acts within that org’s membership scope. Most read commands emit JSON by default; pass --pretty to indent it. The --json flag is accepted everywhere as a no-op for compatibility.
To capture a live webhook or schedule into a versionable YAML file, see track-webhook and track-schedule.
Webhooks
A webhook is an addressable inbound URL. Each webhook owns a list of handlers (what runs when it is hit) and an ordered list of response rules (synchronous replies for handshakes such as the Slack URL-verification challenge). The webhook’s public url is derived at the API edge and returned by get — it is not stored, so always read it back rather than constructing it.
list
List webhooks owned by the current organization.
hutly webhooks list
hutly webhooks list --limit 50 --offset 50 --pretty| Option | Description |
|---|---|
-l, --limit <n> |
Max results (default 100) |
-o, --offset <n> |
Result offset (default 0) |
get
Fetch a single webhook by id, including its derived public url and any response rules.
hutly webhooks get <webhookId> --prettycreate
Create a new webhook. Returns the created record, including its public URL.
hutly webhooks create --name "Slack events" --description "Inbound Slack event subscription"| Option | Description |
|---|---|
--name <name> |
Webhook display name |
--description <description> |
Webhook description |
update
Replace a webhook’s name and/or description. This is a full replacement — fields you omit are cleared.
hutly webhooks update <webhookId> --name "Slack events (prod)"delete
Delete a webhook and all of its handlers. Prompts for confirmation unless -y is passed.
hutly webhooks delete <webhookId> -y| Option | Description |
|---|---|
-y, --yes |
Skip confirmation prompts |
execute
Manually fire a webhook. The payload is delivered as the request body to all of the webhook’s handlers — useful for testing a handler chain without an external caller. Pass at most one of --payload or --payload-file.
hutly webhooks execute <webhookId> --payload '{"event":"test"}'
hutly webhooks execute <webhookId> --payload-file ./sample.json| Option | Description |
|---|---|
--payload <json> |
Inline JSON object payload |
--payload-file <path> |
Read payload JSON from a file |
handlers
Manage the handlers attached to a webhook. A handler invokes either a workflow or an agent when the webhook fires.
hutly webhooks handlers list <webhookId>
hutly webhooks handlers get <webhookId> <handlerId>
hutly webhooks handlers create <webhookId> --workflow <workflowId> \
--input-mapping '{"address":"$.body.property.address"}'
hutly webhooks handlers create <webhookId> --agent <agentId> \
--instructions "Triage the inbound payload and reply in the thread."
hutly webhooks handlers update <webhookId> <handlerId> --instructions "..."
hutly webhooks handlers delete <webhookId> <handlerId> -y| Option | Description |
|---|---|
--workflow <workflowId> |
Workflow id to invoke |
--agent <agentId> |
Agent id to invoke |
--instructions <text> |
Instructions passed to the handler — required for agent handlers, optional for workflow handlers |
--input-mapping <json> |
JSON object mapping target input fields to JSONPath / Handlebars expressions over the request body |
create attaches a handler bound to exactly one of a workflow or an agent. update is a full replacement of the handler’s instructions and input mapping. list supports -l, --limit and -o, --offset; delete supports -y, --yes.
The --input-mapping value maps each target input field to a JSONPath or Handlebars expression evaluated against the inbound WebhookExecutionContext (body, headers, method, path, queryParams, pathParams). Use $. JSONPath for whole-value references and {{ … }} Handlebars for string interpolation.
rules
Manage response rules — synchronous replies evaluated in order before (or instead of) handler dispatch. Rules are matched first-match-wins by index, so order matters. The canonical use is the Slack URL-verification handshake: match on the verification event and echo back the challenge without firing handlers.
hutly webhooks rules list <webhookId>
hutly webhooks rules add <webhookId> \
--name "Slack URL verification" \
--condition "body.type == 'url_verification'" \
--response-body '{"challenge":"{{body.challenge}}"}' \
--skip-handlers
hutly webhooks rules update <webhookId> 0 --name "Slack URL verification"
hutly webhooks rules delete <webhookId> 0 -y| Option | Description |
|---|---|
--name <name> |
Human-readable rule name |
--condition <jexl> |
JEXL expression evaluated against the request (e.g. body.type == 'url_verification'). Omit for a catch-all rule. |
--response-body <json> |
JSON object of string → Handlebars template returned on match |
--skip-handlers |
On match, return the response body without dispatching the webhook’s handlers (recommended for handshakes; default false) |
add appends a rule. update <index> and delete <index> take the rule’s 0-based index from list (which prints rules in match order). update is a full replacement — omitted optional fields are cleared. delete supports -y, --yes.
Schedules
A schedule trigger fires its handlers on a recurring cron schedule. Like webhooks, a trigger owns a list of handlers, each invoking a workflow or an agent.
Cron expressions are AWS EventBridge 6-field Quartz in UTC: minute hour day-of-month month day-of-week year. Exactly one of day-of-month / day-of-week must be ?. Examples:
0 9 ? * MON-FRI *— weekdays at 09:00 UTC*/15 * * * ? *— every 15 minutes
list
List schedule triggers owned by the current organization.
hutly schedules list --prettySupports -l, --limit <n> (default 100) and -o, --offset <n> (default 0).
get
Fetch a single schedule trigger by id.
hutly schedules get <triggerId> --prettycreate
Create a new schedule trigger.
hutly schedules create --name "Weekday digest" \
--description "Morning summary" \
--cron "0 9 ? * MON-FRI *"| Option | Description |
|---|---|
--name <name> |
Schedule display name |
--description <description> |
Schedule description |
--cron <expr> |
EventBridge 6-field Quartz cron (UTC) |
update
Full replacement of a trigger’s name, description, and cron.
hutly schedules update <triggerId> --cron "0 8 ? * MON-FRI *"delete
Delete a schedule trigger and all of its handlers. Prompts for confirmation unless -y is passed.
hutly schedules delete <triggerId> -yhandlers
Manage the handlers attached to a schedule trigger. Identical in shape to webhook handlers, except the input mapping is evaluated over the schedule context rather than a request body.
hutly schedules handlers list <triggerId>
hutly schedules handlers get <triggerId> <handlerId>
hutly schedules handlers create <triggerId> --workflow <workflowId>
hutly schedules handlers create <triggerId> --agent <agentId> \
--instructions "Compile and send the weekday digest."
hutly schedules handlers update <triggerId> <handlerId> --instructions "..."
hutly schedules handlers delete <triggerId> <handlerId> -y| Option | Description |
|---|---|
--workflow <workflowId> |
Workflow id to invoke |
--agent <agentId> |
Agent id to invoke |
--instructions <text> |
Instructions passed to the handler — required for agent handlers, optional for workflow handlers |
--input-mapping <json> |
JSON object mapping target input fields to JSONPath / Handlebars expressions over the schedule context |
list supports -l, --limit / -o, --offset; delete supports -y, --yes.
Manual Tasks
A manual task is the human-in-the-loop approval prompt emitted when a workflow reaches an approval node. The workflow run is suspended until the task is approved (with data matching the task’s output schema) or denied; either response resumes the run.
get
Fetch a manual approval task, including its outputSchema and the prompt payload. Read the outputSchema first — it defines the exact shape respond --data must satisfy.
hutly manual-tasks get <taskId> --prettyrespond
Respond to a manual approval task. Pass exactly one of --approved or --denied.
hutly manual-tasks respond <taskId> --approved --data '{"decision":"accept","notes":"ok"}'
hutly manual-tasks respond <taskId> --approved --data-file ./response.json
hutly manual-tasks respond <taskId> --denied| Option | Description |
|---|---|
--approved |
Approve the task and submit data |
--denied |
Deny the task |
--data <json> |
Inline JSON object matching the task’s outputSchema — required for --approved, ignored for --denied |
--data-file <path> |
Read the response data from a JSON file |
Constraints enforced by the command: --approved and --denied are mutually exclusive (and one is required); --data and --data-file are mutually exclusive; and passing --data / --data-file with --denied is rejected (data is ignored on denial — omit the flag).
Tracking to YAML
The top-level track-webhook and track-schedule commands fetch a single resource by id and serialise it to a YAML file you can version-control and re-deploy. Both wrap the resource id in an !org_map structure so the file can be deployed across organizations.
track-webhook
hutly track-webhook <webhookId>
hutly track-webhook <webhookId> -o ./webhooks/slack-events.yaml| Option | Description |
|---|---|
-o, --output <file> |
Output path (defaults to ./webhooks/<webhook-name>.yaml) |
track-schedule
hutly track-schedule <triggerId>
hutly track-schedule <triggerId> -o ./schedules/weekday-digest.yaml| Option | Description |
|---|---|
-o, --output <file> |
Output path (defaults to ./schedules/<schedule-name>.yaml) |
YAML shape
The generated files strip system fields (createdAt, updatedAt, organizationId) and wrap the resource id in !org_map. A tracked webhook looks like:
webhookId: !org_map
<organizationId>: <webhookId>
name: Slack events
description: Inbound Slack event subscription
responseRules:
- name: Slack URL verification
condition: "body.type == 'url_verification'"
responseBody:
challenge: "{{body.challenge}}"
skipHandlers: trueA tracked schedule wraps triggerId the same way and carries name, description, and the cron expression.