Triggers & Scheduling
Workflows can run on a schedule, fire on an API call, or batch up inbound events through a queue.
Pick the right trigger
| Trigger | When to pick it |
|---|---|
| Manual | You run the workflow from the dashboard or MCP. One-off or exploratory. |
| Cron | Fixed schedule. Daily digest, hourly sync, weekly report. |
| API / webhook | One event = one run. Deploy notifications, form submissions, CI hooks. |
| Queue | Events arrive unpredictably and you want to batch them. Inbound webhooks, email drops, lead captures, IoT telemetry. One batched run every N minutes or every M messages, whichever comes first. |
Queue is the "don't run me once per event" trigger. If you'd rather spend one AI call on 50 events than 50 AI calls on one event each, this is the knob.
Cron Scheduling
Run your workflow on a recurring schedule:
- Open your workflow → Settings
- Find the Schedule section
- Enter a cron expression
- Click Save
Common Cron Expressions
| Expression | Schedule |
|---|---|
0 9 * * 1-5 | Weekdays at 9:00 AM UTC |
0 */6 * * * | Every 6 hours |
0 0 * * 1 | Every Monday at midnight UTC |
*/30 * * * * | Every 30 minutes |
0 8 1 * * | 1st of every month at 8:00 AM UTC |
How It Works
- The schedule triggers on Cloudflare's edge — no server to keep running
- Each trigger creates a new run with the published plan
- You can pass default variables in the schedule configuration
- View scheduled runs in the Runs tab like any other run
- The workflow must be published for the schedule to fire
Disabling a Schedule
Go to workflow Settings → click the delete icon next to the schedule, or unpublish the workflow.
Webhook Triggers
Trigger a workflow from any external service that can send HTTP requests:
POST https://api.cffbrw.com/v1/trigger/:workflowId
Authorization: Bearer wfk_your-api-key
Content-Type: application/json
{
"variables": {
"message": "Deploy completed",
"environment": "production"
}
}
Use Cases
- CI/CD — Trigger a workflow after a deployment completes
- Monitoring — Run analysis when an alert fires
- Form submissions — Process incoming data from web forms
- Third-party integrations — Connect services that support outgoing webhooks
Requirements
- The workflow must be published
- Authentication via
wfk_API key (from Settings → API Keys) - Variables in the request body are available as
{VARIABLE_NAME}in the workflow markdown
API Keys
Manage API keys in the dashboard under Settings → API Keys:
- Click Create Key to generate a new
wfk_prefixed key - Each key is scoped to your workspace
- Keys can only trigger published workflows (403 on drafts)
- Revoke a key by clicking the delete icon
Security: API keys are shown once on creation. Store them securely. They cannot be recovered after dismissing the dialog.
Queue Trigger
Use a queue trigger when events stream in at their own pace and you want one batched run instead of one run per event.
How it works: your workflow gets a persistent inbox at POST /v1/enqueue/:workflowId. External systems post messages there. The platform accumulates them. When either a time window elapses (say, 5 minutes) or a batch size is reached (say, 10 messages), whichever trips first, the platform fires one workflow run with inputs.batch set to the whole batch.
Setting it up
- Open your workflow → Settings
- Find the Queue Trigger section and toggle queue
- Configure:
- Window — how long to wait before firing a batch. 1 second to 1 day.
- Batch size — fire immediately once this many messages arrive. 1 to 100.
- Max queue depth — reject enqueue past this many queued messages. 1 to 10,000.
- Ingest secret — strongly recommended. Without it, anyone who knows the workflow ID can post messages. Set it and include it on each enqueue request.
- Save Configuration and Publish.
Posting messages
curl -X POST https://api.cffbrw.com/v1/enqueue/$WORKFLOW_ID \
-H "X-Webhook-Secret: your-secret" \
-H "Content-Type: application/json" \
-d '{"event": "lead.created", "email": "alice@example.com"}'
Response:
{ "enqueued": true, "queueDepth": 4, "msgId": 42 }
You can post in two shapes:
- Bare — the whole body is the payload.
{ "event": "lead.created" }→ stored payload is{ "event": "lead.created" }. - Envelope — wrap with optional metadata.
{ "payload": {...}, "source": "webhook-A", "dedupeKey": "order-123" }→ stored payload is the inner object,sourceanddedupeKeyare envelope fields.
Deduplication: set dedupeKey in the envelope to prevent double-processing when your producer retries. If a message with the same key is already in the queue (not yet drained), enqueue returns 409 DEDUPE instead of inserting a duplicate. The key is scoped to the queue and reusable after the message drains.
Writing the workflow
The workflow receives inputs.batch as an array of { id, payload, received_at, source? } objects. Typical shape:
# Lead digest
Summarize today's inbound leads and post to Slack.
## Step 1 — Filter qualified leads
`inputs.batch` is an array of new leads. Keep only those with `score >= 7`.
\`\`\`transform
return data.input.batch.filter(m => m.payload.score >= 7);
\`\`\`
## Step 2 — Summarize
Generate a digest from the filtered leads.
## Step 3 — Post to #sales
Send the digest via Slack.
The compiler will warn you if you publish a queue-triggered workflow that never references inputs.batch — almost always a misconfiguration.
Behavior notes
- Serial execution. While a batch is being processed, new messages still accumulate, but the next drain waits until the current run finishes. Simpler to reason about for workloads that dedupe or aggregate against shared state.
- Backpressure. Once queue depth hits
maxQueueDepth, enqueue returns429with the depth details. Well-behaved webhook producers retry on 429, which gives you natural backpressure. - Dead-letter on failure. If a dispatched run fails, its messages go to a dead-letter queue (not auto-retried). Inspect at
GET /v1/queues/:id/dead-letter, replay viaPOST /v1/queues/:id/dead-letter/replay. Replay preserves original timestamps; if replaying would push the queue past its depth cap, it returns409instead of silently losing data. - Batch too large. If a batch's serialized size would exceed the platform's per-run limit, it's diverted to the dead-letter queue with
failure_reason="batch_too_large". LowermaxBatchSizeif you see this.
Inspect the queue
GET /v1/queues/:workflowId→ depth, in-flight run id, next fire time, dead-letter countGET /v1/queues/:workflowId/messages?limit=50→ queued message listGET /v1/queues/:workflowId/dead-letter?limit=50→ dead-letter entriesDELETE /v1/queues/:workflowId/messages/:id→ remove a single queued messageDELETE /v1/queues/:workflowId→ purge all queued messages (admin escape hatch)
Inspection endpoints use your Clerk session (dashboard auth). Enqueue uses the workflow's ingest secret.
Combining Triggers
A single workflow can have multiple trigger types active simultaneously:
- Manual runs from the dashboard
- A cron schedule for daily execution
- A webhook endpoint for on-demand triggers
- A queue trigger for inbound event streams
All triggers use the same published plan and create independent runs.