Skip to main content
Two trigger sources need no provider connection: heartbeat triggers wake an agent on a cron schedule, and custom webhook endpoints give any external system — an alerting tool, a payment provider, your own backend — an authenticated URL that fires an agent trigger. This page is the reference for both.

Heartbeat (cron) triggers

A heartbeat trigger is declared with kind: heartbeat instead of event::

Fields

"heartbeat"
required
Marks the trigger as a schedule. event, events, connection, optional, endpoint, and auth are all rejected on a heartbeat trigger, and checks: is rejected too (checks are legal only on github.pull_request.* events).
string
required
The cron expression, 1–512 characters. auto passes it verbatim to a Temporal Schedule, so use the classic 5-field form minute hour day-of-month month day-of-week — for example 0 9 * * 1-5 for 9:00 on weekdays.
string
default:"UTC"
IANA timezone name (e.g. America/Los_Angeles) the cron expression is evaluated in.
message, where, fallback, and routing work exactly as on event triggers.

How ticks run

Applying the agent creates one durable schedule per heartbeat trigger, reconciled on every apply — editing the cron updates the schedule, deleting the trigger (or the agent) deletes it. Each trigger fires under a synthetic event key of the form heartbeat.<agentResourceId>.<ordinal>, which only that trigger can match — one agent’s heartbeat never wakes another agent. Operational behavior worth knowing:
  • No overlap: if a tick’s dispatch workflow is still running when the next tick is due, the new tick is skipped rather than stacked.
  • Catch-up window: ticks missed during a short outage are replayed within a 1-minute window; older missed ticks are dropped. Each tick is deduplicated on <scheduleId>:<scheduledAt>, so a replay never fires twice.
  • Self-cleaning: a tick whose agent no longer exists deletes its own schedule instead of erroring forever.

Payload and placeholders

Every tick delivers this payload:
{{heartbeat.scheduledAt}} is the placeholder that matters: it is the tick’s scheduled time (not the delivery time), so use it as the anchor for reporting windows — “the 24 hours ending at {{heartbeat.scheduledAt}}” stays exact even if delivery lags.

Routing patterns

  • routing: { kind: spawn } — one fresh session per tick. The right default for reports and digests.
  • routing: { kind: deliver, onUnmatched: spawn } — for a standing agent with concurrency: 1: the tick is delivered into the agent’s one live session, and spawns it if none is running. A plain deliver with no routeBy is only legal on a concurrency: 1 agent (or when onUnmatched: spawn can claim the slot).

Example: a daily digest

Adapted from the daily digest example:
.auto/agents/ship-digest.yaml

Custom webhook endpoints

A trigger that listens on a webhook.* event declares its own HTTP endpoint inline: endpoint: names it, auth: says how callers authenticate.

Provisioning

Applying the agent provisions the endpoint and returns a receipt per webhook trigger — { event, endpoint, ingestUrl, status: "ready" } — with the public ingest URL:
The slug is stable, so the URL can be pasted into the external system once. The endpoint name scopes trigger selection: several triggers (even on different agents) can share one endpoint, and an inbound event is matched only against the triggers bound to that endpoint.

Authentication

"hmac_sha256" | "bearer_token" | "none"
How inbound requests authenticate. hmac_sha256 and bearer_token require secretRef. auth is required on the apply that first creates the endpoint; a later trigger binding an existing endpoint may omit it and inherit the endpoint’s auth, and declaring different auth for an existing endpoint fails apply.
string
The name of a project secret holding the shared credential. The plaintext is resolved server-side at delivery time; rotating the secret rotates the endpoint’s credential without re-applying.
Both credentialed kinds compare in constant time. CORS is wide open on the ingest route (auth is header-based, never cookie-based), so a browser page can POST to a bearer endpoint directly.

Request contract

The body must be a JSON object. Two optional top-level fields have meaning to auto; everything else is your payload:
string
Names the event key: a body with "event": "incident.opened" routes as webhook.incident.opened (a value already starting with webhook. is kept verbatim). A body without event routes as the fixed fallback key webhook.received.
string
Idempotency key. A repeated dedupKey returns the original event record and does not re-route. Without it, every delivery is a fresh event.
Responses: 202 with { eventRecordId, created: true, routerStatus } on first delivery; 200 with created: false on a dedup hit; 401 on bad credentials; 400 on a non-object or unparseable body.

Payload, placeholders, and filters

The trigger payload is the raw request body — there is no normalization. Template placeholders and where paths address whatever the caller posts:

Catching unshaped providers

Most third-party webhook senders do not put an event field at the top level. Point them at the endpoint anyway and author a fallback: true trigger on webhook.received; it fires only when no normal trigger on the endpoint matched, and where filters on the provider’s own body discriminate from there:

Diagnostics

Sessions inspect endpoints with the auto.webhooks.list and auto.webhooks.get tools, which report each endpoint’s ingest URL, auth mode, secret presence, attached triggers, and problems: a credentialed endpoint with no secretRef, or a secretRef naming a nonexistent secret, is an error; an endpoint with no active triggers or a not-yet-promoted reservation is a warning.

Example: incident response

Adapted from the incident response example:
.auto/agents/incident-response.yaml
Create the secret before applying, then send the recorded value as the bearer token from the alerting tool.

See also

  • Triggers reference — routing kinds, filter grammar, fallback semantics
  • Secrets — creating the secretRef values webhook auth resolves
  • Lifecycle events — the internal auto.* events that need no endpoint at all