> ## Documentation Index
> Fetch the complete documentation index at: https://control-dev.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Trigger workflows

> Choose when an agent workflow runs: manual test runs, cron schedules with a preview, Control events like month-close readiness, or signed external events.

# Trigger workflows

Triggers decide when Control creates an *occurrence* for an agent workflow. An occurrence is a deduplicated, at-most-once instruction to dispatch a run. You can attach several triggers to the same workflow.

## Trigger types

| Trigger                   | Best for                                                      | Deduplication key                                                                 |
| ------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| **Manual**                | Testing a draft, ad-hoc runs, and demos.                      | New occurrence every time you press **Run now**.                                  |
| **Daily / cron schedule** | Recurring reports and digests.                                | One occurrence per scheduled tick, calculated in the workflow's time zone.        |
| **Month ready to close**  | Kicking off close-time reporting the moment finance is ready. | One occurrence per accounting period that emits `finance.monthly_close.ready.v1`. |
| **New counterparty**      | Continuous review of new business partners.                   | One occurrence per counterparty that emits `finance.counterparty.identified.v1`.  |
| **Signed external event** | Reacting to another system (webhook-style, HMAC-signed).      | One occurrence per unique dedupe key supplied in the event payload.               |

Manual is always on for every workflow. Add other triggers in the Builder's **Triggers** section.

## Manual

Manual triggers are always available. Use them to run a workflow immediately with optional test input.

1. Open the workflow in the **Builder**.
2. Enter optional test input in the run field.
3. Select **Run now**.

Manual runs create one occurrence and dispatch a run right away. They ignore the pause state, so you can test paused workflows without resuming them.

## Cron schedule

Use a cron schedule for recurring reports and digests that should run at a fixed time in a specific time zone.

Configuration:

* **Five-field cron.** Standard cron with minute, hour, day of month, month, and day of week fields, for example `0 8 * * *` for 08:00 every day.
* **IANA time zone.** The zone the schedule runs in, for example `Europe/Helsinki`. Control adjusts for daylight saving time in that zone.

As soon as both fields are valid, the Builder shows a **Next runs** preview so you can confirm the schedule matches your expectations.

Occurrences are deduplicated per scheduled tick per published version, so restarting the scheduler cannot produce the same tick twice.

Example schedules:

```text theme={null}
0 8 * * *        # Every day at 08:00 in the configured time zone
0 8 * * 1-5      # Weekdays at 08:00
0 9 1 * *        # 09:00 on the first day of each month
```

## Control events

Control emits domain events as finance data changes. A workflow can subscribe to one or more of these events; each unique event instance produces exactly one occurrence.

| Event                                | When it fires                                                                           |
| ------------------------------------ | --------------------------------------------------------------------------------------- |
| `finance.monthly_close.ready.v1`     | A tenant's monthly close-readiness checks pass, indicating the month is ready to close. |
| `finance.counterparty.identified.v1` | A new counterparty is identified in the tenant's transaction history.                   |

Enable a Control event trigger from the **Triggers** section of the Builder. Nothing further is required: the trigger fires from Control's own event stream on the currently published version, and paused workflows skip the event without losing dedupe history.

## Signed external event

Use a signed external event to accept HTTP notifications from systems Control does not natively integrate with. Requests carry an HMAC-SHA256 signature over the raw body and are bound to the tenant that owns the event credential.

Configuration in the Builder:

* **Event type.** The `eventType` value your caller will send, for example `finance.external.updated.v1`.
* **Optional bound connection.** Restrict the trigger to events that arrive on a specific tenant connection, or accept any signed source using a tenant event key.

Signed external events require an active event credential. See [Connect Slack, Gmail, and event sources](/product-docs/agents/connections) for how to create and revoke one.

### Sending a signed event

Post the event to `/agent-events` on your Control API host. Each request must include:

* `Authorization: Bearer <token>`, where `<token>` has the form `cae.<prefix>.<secret>`. The token is shown once when the credential is created and cannot be retrieved later.
* `X-Control-Event-Timestamp: <unix seconds>`, the current time as a Unix epoch integer. Requests more than five minutes off the server clock are rejected.
* `X-Control-Event-Signature: <hex>`, the lowercase hex of `HMAC-SHA256(secret, "<timestamp>." + <raw body>)`.

The request body is JSON:

```json theme={null}
{
  "eventType": "finance.external.updated.v1",
  "dedupeKey": "invoice-2026-08-11-12345",
  "payload": { "invoiceId": "12345", "status": "posted" },
  "connectionId": null
}
```

Fields:

* `eventType` must match the value configured on at least one active workflow trigger.
* `dedupeKey` uniquely identifies the event instance. Retrying with the same key does not produce a second occurrence.
* `payload` is passed to the workflow.
* `connectionId` is optional; if set, only triggers bound to that connection receive the event.

Successful requests return `202 Accepted` with the ingest result and any inline dispatches. Invalid signatures, timestamps, or credentials return `401 Unauthorized`. Bodies larger than 64 KiB or invalid JSON return `400 Bad Request`.

### Example signed request

```bash theme={null}
TOKEN="cae.<prefix>.<secret>"
SECRET="<secret>"
TS="$(date -u +%s)"
BODY='{"eventType":"finance.external.updated.v1","dedupeKey":"invoice-2026-08-11-12345","payload":{"invoiceId":"12345"}}'
SIG="$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')"

curl -X POST https://api.control.dev/agent-events \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Control-Event-Timestamp: $TS" \
  -H "X-Control-Event-Signature: $SIG" \
  -H "Content-Type: application/json" \
  --data "$BODY"
```

## Occurrences and runs

An occurrence is Control's record that a trigger fired. Each occurrence dispatches at most one run of the currently published version. If a run fails to start because of a transient issue, the scheduler retries the occurrence. Duplicate ticks, events, or external deliveries with the same dedupe key are ignored.

Inspect occurrences and their runs in the **Runs** view. See [Review runs and approvals](/product-docs/agents/runs-and-approvals).

## Related tasks

* [Build and publish a workflow](/product-docs/agents/build-and-publish)
* [Connect Slack, Gmail, and event sources](/product-docs/agents/connections)
* [Review runs and approvals](/product-docs/agents/runs-and-approvals)
