> ## Documentation Index
> Fetch the complete documentation index at: https://app.keystroke.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Overview

> Connect cron, webhook, and poll sources.

A trigger is how a [workflow](/learn/workflows/overview) or [agent](/learn/agents/overview) runs automatically. It binds a **source** (a schedule, an inbound webhook, or a poll) to a target, so every matching event starts a run. Triggers hold no business logic: just the schedule, endpoint, validation, and filters. The work lives in the workflow or agent they attach to.

Use a schedule when the workflow should run **every** tick unconditionally, a webhook when another system can push an event to Keystroke, and a poll when Keystroke should check an external system and run **only when a condition is met** (a filtered poll tick creates no run).

## Example requests

Ask your coding agent when an automation should start. It can wire up the source, target, filters, and payload shape.

> "Build an agent that sends me a morning brief every weekday at 9am in Slack."

> "When a Stripe payment succeeds, update the customer record in our CRM and start the Zendesk onboarding workflow."

> "Check our vendor API every hour, and run the pending invoice workflow when a new invoice is marked as 'ready to process'."

## Source plus attach

Triggers live in your project code under `src/triggers/`. Each file defines a source, then chains `.attach()` to bind it to a workflow or agent, and **default-exports** the result.

```ts src/triggers/signup.ts theme={null}
import { defineWebhookSource } from "@keystrokehq/keystroke/trigger";
import { z } from "zod";
import workflow from "../workflows/signup-pipeline";

export default defineWebhookSource({
  slug: "signup",
  endpoint: "signup",
  request: z.object({ name: z.string(), email: z.string().email() }),
}).attach({ workflow });
```

The runtime discovers the file when you run or deploy the project. Only the default export is discovered — a single `.attach()`, a chain of `.attach()` calls, or an array of attachments (see [fan out to multiple targets](/learn/triggers/advanced-triggers#fan-out-to-multiple-targets)).

## The three sources

There are exactly three trigger source types. Each takes a `slug` (the stable trigger slug) plus options specific to how it fires.

| Source       | Define with                                       | Fires when                                                                                   |
| ------------ | ------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| **Schedule** | [`defineCronSource`](/learn/triggers/schedules)   | A cron schedule comes due — every tick dispatches all enabled attachments (no filters)       |
| **Webhook**  | [`defineWebhookSource`](/learn/triggers/webhooks) | A request `POST`s to the trigger's endpoint and matches its schema                           |
| **Poll**     | [`definePollSource`](/learn/triggers/polling)     | A scheduled `run()` returns a payload that passes its filters — filtered ticks create no run |

To react to events from third-party apps (Stripe, GitHub, Linear, and so on), you point one of these sources at the app, usually a webhook and sometimes a poll. See [app events](/learn/triggers/app-events).

## Attach to a workflow or an agent

`.attach()` binds the source to one of two targets:

```ts theme={null}
// Workflow target: the payload becomes the workflow input
.attach({ workflow, transform: (payload) => ({ /* … */ }) })

// Agent target: the payload becomes a prompt
.attach({ agent, prompt: (payload) => `Handle ${payload.id}` })
```

A workflow target accepts an optional `transform` to shape the payload into the workflow's input. An agent target takes a `prompt` (a string, or a function of the payload) instead. See [advanced triggers](/learn/triggers/advanced-triggers) for both in depth.

## Attachment id

Each attachment has an id of the form `{sourceSlug}:{targetSlug}`, the source `slug` joined to the workflow or agent `slug`:

```
signup:signup-pipeline      # webhook → workflow
morning-check:support       # cron → agent
```

You use this id to inspect trigger-driven runs in [run history](/learn/logs/overview) and from the CLI.

## Filters live on the source, transform on the attachment

Keep the two concerns separate:

* **Filters** decide *whether* to run, and are part of the **source**: a webhook's `request`/`filter` Zod schemas, or a poll's `filter` predicates.
* **`transform`** decides *what input the run receives*, and is part of the **attachment** (workflow targets only).

This split keeps "should this run?" next to the source definition and "what does the run get?" next to the binding. See [advanced triggers](/learn/triggers/advanced-triggers).

## Disable individual attachments

Each attachment can be paused without removing it from your project code. Disabled attachments stay visible in the dashboard and API, but they do not match webhook ingress, receive cron ticks, or receive poll results.

* Disable one workflow or agent binding while leaving sibling attachments on the same source active.
* Re-enable the attachment later without redeploying.
* The disabled state survives redeploys — a later deploy does not re-enable a paused attachment.
* When every attachment on a scheduled source is disabled, its schedule stops firing until at least one attachment is enabled again.

**Deploy timing** — webhooks are passive and safe to deploy any time. A poll fires immediately on deploy; a cron waits for its next scheduled slot, then runs unattended. Until the workflow is verified, disable the attachment rather than removing it from code.

Use the trigger detail panel in the web app, the platform API (`PATCH /api/triggers/:triggerId/attachments/:attachmentId`), or the CLI:

```bash theme={null}
keystroke triggers disable <trigger-slug>                     # pause every attachment
keystroke triggers disable <trigger-slug> --workflow <slug>   # pause one workflow attachment
keystroke triggers disable <trigger-slug> --agent <slug>      # pause one agent attachment
keystroke triggers enable <trigger-slug>                      # resume
```

Address the trigger by id or slug. To pause a single attachment on a multi-target trigger, narrow with `--workflow`/`--agent` — you never need the attachment id.

## Inspect trigger runs

Triggers are typically operated against a deployed (cloud) project. List them, print a webhook URL, run a poll on demand, and audit runs from the CLI:

```bash theme={null}
keystroke triggers list
keystroke triggers url signup
keystroke triggers runs list signup --workflow signup-pipeline
```

See the [CLI reference](/cli#triggers) for every trigger command and [run history](/learn/logs/overview) in the web app.

## Next steps

<CardGroup cols={2}>
  <Card title="Schedules" href="/learn/triggers/schedules">
    Run a workflow or agent on a cron schedule.
  </Card>

  <Card title="Webhooks" href="/learn/triggers/webhooks">
    Run on inbound HTTP requests with a validated payload.
  </Card>

  <Card title="Polling" href="/learn/triggers/polling">
    Periodically check a source and run when there's work.
  </Card>

  <Card title="App events" href="/learn/triggers/app-events">
    React to events from connected third-party apps.
  </Card>
</CardGroup>
