Start here
How a webhook starts a Task
Task triggers match a source plus event type and optional filters across your workspace; the first matching entry starts a multi-step run alongside delivery.
Tasks are started by triggers, not by destinations. When Hooks accepts an inbound event, every enabled task in the workspace checks its trigger list; a match starts a multi-step run, in parallel with normal delivery.
The trigger model
Section titled “The trigger model”A trigger is a list of entries. The first entry that matches the event starts the run:
trigger: - source: github # provider name, or "*" for any source event: issues.labeled # normalized <resource>.<action>, or "*" for any event filter: # optional, AND semantics - "{{event.label.name}} == 'agent'" throttle: # optional per-key rate limit key: "{{event.issue.number}}" max: 1 window_minutes: 60 - source: stripe event: invoice.paid| Field | Meaning |
|---|---|
source |
The provider the event came from: github, stripe, slack, linear, sentry, generic, or * (any). |
event |
The normalized event type as <resource>.<action>, for example issues.labeled or invoice.paid, or * (any). |
filter |
Optional expressions, all of which must pass. See the grammar below. |
throttle |
Optional { key, max, window_minutes } rate limit applied after a match, per rendered key. |
Starting on a schedule
Section titled “Starting on a schedule”A task can also start on a cron schedule. Add a schedule block — a task needs an event trigger, a schedule, or both:
schedule: cron: "0 9 * * mon-fri" # minute hour day-of-month month day-of-week timezone: Asia/Tokyo # IANA zone, not UTC-only- Five fields only.
0/7is Sunday; ranges (mon-fri), lists (1,15) and steps (*/15) work. - Times are wall-clock in the named zone. A spring-forward gap is skipped, and a repeated fall-back hour runs once, at the first occurrence.
- Each tick starts one run, stamped with
triggeredBy: scheduleand the tick time. If the platform was down, the missed ticks collapse into one catch-up run instead of a burst. - Disabling the task stops new ticks; runs already in flight finish.
Matching is workspace-wide
Section titled “Matching is workspace-wide”A trigger listens for a source + event type anywhere in the workspace, not to one endpoint or destination. If two endpoints both receive GitHub events, a github · push trigger can start a run from either one. Multiple tasks can match the same event, and each starts its own run. Several entries matching at once never start more than one run per task.
Filter grammar
Section titled “Filter grammar”Filters are deliberately small so a typo fails at save time instead of silently never matching:
{{event.label.name}} == 'agent'{{event.sender.is_collaborator}} == true{{event.issue.pull_request}} != null{{event.issue.draft}} # truthy- Only
{{event.*}}paths resolve, against the source’s raw payload. - Operators are
==,!=, and bare truthiness; entries in afilterlist are ANDed. - Anything more complex belongs in a step or a destination filter.
Template variables
Section titled “Template variables”Step arguments, if conditions, throttle keys and wait.callback bodies all render {{...}} expressions. Each root resolves against a different object, and a path that does not exist fails the run with Missing template path <path> in the <scope>.
| Root | What it is | Example |
|---|---|---|
event |
The raw source payload — exactly what the sender posted. Not the Hooks envelope. | {{event.issue.number}} |
hooks |
Hooks metadata for the event that started the run. | {{hooks.event_id}} |
steps |
Outputs of earlier steps, keyed by step id. | {{steps.create.machine_id}} |
secret |
Named workspace secrets in scope for the step. | {{secret.GITHUB_TOKEN}} |
var |
Plaintext variables of the run’s environment. | {{var.REGION}} |
run |
Run state: id, token, status, error, log_tail, callback_url. |
{{run.id}} |
{{hooks.event_id}} is the stored event id (evt_…). It is empty for runs that were not started by an event — scheduled runs, manual runs and reruns of a cron run.
Two traps worth knowing:
{{event.id}}is not the Hooks event id. It readsidfrom the sender’s payload and fails withMissing template path event.id in the raw source payloadwhen the payload has noid— which is what most senders look like.event.*cannot see normalized fields a destination would. If you needsubject.titleor an event type, use{{hooks.…}}plus the raw paths, or forward to a destination whose template already normalizes.
A dry run in the editor renders every step against the sample payload (or a stored event you pick), so a missing path shows up there before the task is saved.
How this differs from delivering to an agent
Section titled “How this differs from delivering to an agent”Forwarding an event to an agent destination (or the /tools/payload-to-task helper) delivers the payload once — delivery templates shape the request body, and there is no run. A Task trigger starts a durable, multi-step run with steps, retries, timeouts, and {{steps.*}} state. Use a trigger when you want work to happen; use a destination when you only want a payload delivered.
Creating and testing a trigger
Section titled “Creating and testing a trigger”- Open the console → Tasks → create a task, and fill the Trigger section: one or more
source + evententries with optional filters and throttle. - The editor saves the YAML and shows a trigger summary; use a dry run in the editor to validate steps without side effects.
- Real runs start from an inbound event that matches — send a test request with the trigger’s source and event type, then watch the run on the Runs page.
Example: GitHub issue labeled agent
Section titled “Example: GitHub issue labeled agent”trigger: - source: github event: issues.labeled filter: - "{{event.label.name}} == 'agent'" - "{{event.sender.is_collaborator}} == true"A collaborator adding the agent label to an issue starts the task; the issue title, body, and URL are available to steps as {{event.subject.*}} and {{event.raw.*}}.