A trigger decides when a workflow starts. Every trigger enters the same run model, so run history, approvals, and credits behave the same way whichever trigger you choose.
Trigger kinds
| Trigger | Starts when | Typical use |
|---|---|---|
| Manual | You choose Run in the builder | Testing a new process |
| Schedule | A cron expression matches in the workflow’s timezone | Weekly reviews, daily digests |
| Event | A workspace event fires: a ticket is decided, brand enrichment completes, an inbox or chat channel receives a message, a monitored competitor page changes, or a table row is created, updated, or deleted | Reacting to work inside AI Agent |
| App event | A connected app fires (Stripe, Slack, Linear, Google Sheets, …). Pick the connection, the event, fill the event’s settings; the workflow receives the provider object under input.payload. |
Reacting to work in another product |
| Poll | A connected app is checked every N minutes (5 minimum); one run starts per new item (or one run per batch). Turning the trigger on remembers what already exists — only items that arrive afterwards start runs. Daily limit 500 runs. | Google Sheets: new rows, and similar “watch a list” sources |
| Webhook | An external system sends an HTTP POST to the workflow’s URL |
Form submissions, CRM automations, custom code |
Set the trigger from the Start block in the builder, or from the Autopilot form. A schedule, event, or webhook trigger only fires while the workflow is active.
Only run when. An event trigger can carry a filter such as input.source == 'human'. The workflow runs only when the filter is true for the event. Table-row triggers can also be limited to one table. The event payload carries ids and metadata, not the row’s values; to test a row’s field, add a Query table step and a Condition after the trigger.
Google Sheets: new rows. Pick a Poll trigger, connect the sheet, and set the interval (once a week is fine for a digest). The sheet needs a column that uniquely identifies each row, such as an id or a timestamp — the trigger uses it to tell new rows from ones it already saw. Each new row starts its own run with that row under input.item, or turn on batch mode to get every new row at once under input.items.
Webhook requests
Each webhook-triggered workflow has its own URL. Copy it from the Start block.
- Method:
POST. - Body: any text up to 1 MB. When the
Content-Typeis JSON, the body is parsed and exposed to the workflow as its input; otherwise the raw text is exposed. - Delivery id: send an
Idempotency-Key,Webhook-Id, orX-Request-Idheader to make retries safe. Without one, the SHA-256 of the body is used, so an identical body is treated as a duplicate. - Limit: 60 requests per minute per webhook and sender address. Above that you receive
429with aRetry-Afterheader.
Responses
| Status | Body | Meaning |
|---|---|---|
200 |
{ "ok": true, "status": "queued", "deliveryId": "…" } |
Accepted; a run starts shortly. |
200 |
{ "ok": true, "status": "duplicate", "deliveryId": "…" } |
Same delivery id already accepted; no new run. |
202 |
{ ok, status: "suspended", deliveryId } |
Sync mode: the run is waiting for a person. |
401 |
Unauthorized |
No valid token or signature. |
404 |
Workflow webhook not found |
Unknown, archived, or non-webhook workflow. |
408 |
{ ok, status: "timeout", deliveryId } |
Sync mode: the run did not answer in time (or did not start); if it is running, it keeps running. |
413 |
Payload too large |
Body over 1 MB. |
429 |
Too Many Requests |
Rate limit reached. |
500 |
{ ok: false, status: "failed", deliveryId } |
Sync mode: the run failed. |
The response never includes a run id, except in sync mode below. Open the workflow’s run history to follow the run.
The delivery-id dedupe above only catches an identical retry. If the same real-world event can arrive under a different delivery id — no stable Idempotency-Key from the sender, or a re-send with a new one — add a Deduplicate step after the trigger and key it on a field from the body, such as an order id. It remembers that key for the window you set and skips or flags the repeat.
Wait for the result
Add ?mode=sync to the webhook URL to wait for the workflow’s answer in the same request. The workflow replies through a Respond to webhook block (JSON, raw text, or a redirect, with a status code and headers of your choice). Without that block, a finished run returns 200 with status: "completed" and the workflow’s final output when it defines one. The wait is limited to 25 seconds by default; a slower run returns 408 with status: "timeout" and the same deliveryId, and keeps running — find it in run history. A run waiting for approval returns 202 with status: "suspended". A failed run returns 500 with status: "failed". Every reply carries the X-Webhook-Delivery-Id header.
Choose Respond and stop to end the run after replying, or Respond and continue to reply and keep working. Only requests sent with ?mode=sync receive the reply; others get the usual queued receipt.
Authenticate a webhook
A request is accepted when it carries either a valid token or a valid signature. Requests with neither are rejected before the body is read.
Every webhook trigger has a token that starts with whsec_. Send it in one of two headers:
Authorization: Bearer <token>X-Webhook-Token: <token>
Use the token with tools that let you add a fixed header, such as Zapier or Make.
Turn on Require a body signature in the Start block to get a signing secret that starts with whsig_. The sender computes an HMAC-SHA256 of the exact request body with that secret and sends it in the header you choose. The default header is X-Signature-256 and the default encoding is hex. Choose base64 for services that send base64 signatures.
The header value may start with sha256=. Both forms are accepted:
X-Signature-256: sha256=f7bc83f4…X-Signature-256: f7bc83f4…
Node.js example:
import { createHmac } from "node:crypto"
const body = JSON.stringify({ email: "[email protected]" })
const signature = createHmac("sha256", process.env.WEBHOOK_SIGNING_SECRET)
.update(body)
.digest("hex")
await fetch(WEBHOOK_URL, {
method: "POST",
headers: {
"content-type": "application/json",
"x-signature-256": `sha256=${signature}`,
},
body,
})Shell example:
BODY='{"email":"[email protected]"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SIGNING_SECRET" | awk '{print $NF}')
curl -X POST "$WEBHOOK_URL" \
-H "content-type: application/json" \
-H "x-signature-256: sha256=$SIG" \
--data "$BODY"Sample data
AI Agent keeps the last five payloads a webhook, app-event or polling trigger received. Open the Start block to see them, pin one, and click Use fields to add its fields to the input contract so you can pick them in any step. Catch a test event waits two minutes for the next payload; for a polling trigger it fetches the current items right away. A step test and Run with sample use the pinned sample as the workflow input. Long values are shortened to keep a sample under 16 KB.
Resume a waiting run
A step that waits for a URL can suspend a run until an external caller sends POST to the run’s own resume URL. Present the resume token one of three ways, checked in this order: Authorization: Bearer <token>, an X-Resume-Token header, or ?token= on the URL. Prefer a header — a query-string token is more likely to end up logged somewhere.
The route responds 202 on a successful resume, 401 on a missing or invalid token, and 409 when the run is not currently waiting there (already resumed, canceled, or never suspended).
Rotate a credential
Rotate in the Start block
Choose Rotate token or Rotate signing secret. A new value is generated immediately.
Update the sender
Paste the new value into the external system. The previous value keeps working for 24 hours so the change does not drop deliveries.
Revoke immediately when needed
If a credential leaked, tick Revoke the current token immediately in the rotation dialog. The previous value stops working at once.
Verify
Send one test request with the new value and confirm a queued response and
a new run in run history.
Every rotation is recorded in the workspace audit log without the secret values.
Fix a rejected request
Check that the header is Authorization: Bearer <token> or X-Webhook-Token, that the token starts with whsec_, and that it was not rotated more than 24 hours ago.
Confirm signing is enabled on the trigger, the header name matches the trigger setting, the encoding (hex or base64) matches, and the signature was computed over the exact bytes sent.
The delivery id was seen before. Send a new Idempotency-Key for each distinct event.
The workflow is archived, the URL is wrong, or the trigger is no longer a webhook. Copy the URL again from the Start block.