# Workflow triggers

Choose manual runs, schedules, one-time runs and managed events with stable trigger keys.

[Deploy your workflow](/deploy)

[Start for free](https://app.whenever.dev/)

## Trigger builders

These go in the manifest, under triggers. Every one takes a required key: the trigger's stable identity, unique within the workflow, and what a run reads back as ctx.trigger.key.

| Builder                                   | Shape and constraints                                                                                                              |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `manual({ key })`                         | Run on demand, with no automatic trigger.                                                                                          |
| `schedule(cron, { key, tz? })`            | Five whitespace-separated cron fields, or @hourly / @daily / @weekly / @monthly. Six-field, seconds-granular cron is rejected.     |
| `once(iso, { key })`                      | One absolute ISO 8601 instant, such as "2026-08-01T09:00:00Z". A bare time of day is rejected; the runtime enforces "still ahead". |
| `hourly({ key, minute?, tz? })`           | minute is 0–59, default 0.                                                                                                         |
| `daily({ key, at?, tz? })`                | at is "HH:MM" on a 24-hour clock, default "00:00". Seconds are not accepted.                                                       |
| `weekly({ key, day?, at?, tz? })`         | day is a lowercase weekday name, default "monday".                                                                                 |
| `monthly({ key, dayOfMonth?, at?, tz? })` | dayOfMonth is 1–31, default 1. A day past the end of a short month does not fire that month.                                       |
| `every("<n>m" \| "<n>h", { key, tz? })`   | The interval has to divide its field evenly: minutes divide 60, hours divide 24.                                                   |
| `webhook({ key, name? })`                 | Unverified inbound HTTP: any request to the endpoint starts a run. The URL is bound to the key; name is display text only.         |
| `event({ key, source, config? })`         | A managed provider event, with source and config taken from Whenever's catalogue.                                                  |

tz is recorded on the trigger and applied by the runtime, which fires at minute granularity and never early. Changing a webhook's key issues a different URL, so keep keys fixed across edits.

A manual run carries the trigger the person chose, so a workflow with several triggers can tell every path apart by ctx.trigger.key. A managed event looks like `event({ key: 'new-mail', source: 'gmail.new-gmail-message', config: { labels: ['INBOX'] } })`, and its source and every config field come from the catalogue.

## Choosing how a run starts

A scheduled poll starts a run even when the provider has no new records. An inbound webhook or managed event starts an execution when a qualifying delivery arrives. Neither approach implies a universal message-to-run conversion or polling cadence.

Use a stable key for each trigger and branch on `ctx.trigger.type` or `ctx.trigger.key` when different triggers need different logic. A schedule supplies no delivery body; do not assume `ctx.input` contains webhook data on that path.

## Related documentation

- [Scheduled workflow examples](/docs/schedule-examples)
- [Several triggers in one workflow](/docs/multiple-triggers)
- [Plain and signed webhooks](/docs/webhooks)
- [Runtime limits and responsibilities](/docs/runtime-limits)
