Event Bus / Triggers
How workflows start — webhooks, schedules, and event sources.
What it is
Triggers are the entry points that start a workflow. TurfAI has four: webhook, scheduled (cron), manual, and event sources via a unified Event Bus (push-based, starting with Google Drive folder watching and extensible to email/Slack). The Event Bus turns external events into workflow runs without polling, and writes every event to an audit log.
When to use it
- Webhook — start from an external system's HTTP POST (JSON, form-data, or file upload). Best when the other system already knows when something happened (a form was submitted, a record changed).
- Scheduled — recurring jobs (cron, timezone-aware). Best for time-driven work: nightly reports, weekly reminders, end-of-month reconciliation.
- Event source — react to "a file landed in this Drive folder". Best when you own the folder but not the upload event — TurfAI watches Drive for you.
- Manual — run on demand with custom JSON inputs. Best for testing and one-off backfills.
Picking a trigger
If an external app can call a URL, prefer a webhook — it is the most direct and gives you a polling token back. If nothing can call you but the data shows up on a clock, use a schedule. If files arrive in a Drive folder, use an event source so you don't have to instrument the uploader.
How it works
Process flow — event to execution:
Call flow — a Drive push trigger:
Sub-features
- Webhook — secret-key auth (timing-safe), file uploads, auto-generated curl samples, polling token, event-type tagging, secret rotation with a grace window, optional origin pinning.
- Scheduled — cron, timezone-aware, pause/resume (
is_active), skip-if-running guard, next-run prediction. - Event sources (Pub/Sub) — Drive folder watch, subscriptions with filters, event logs, auto watch renewal, volume stats.
- Manual — run with custom JSON inputs.
Configuration parameters
Webhook (api::webhook.webhook)
| Field | Type | Default | Notes |
|---|---|---|---|
name | string | — | Required. Human-readable name. |
secret_key | string | auto | Required, private. 64-char hex; sent back as X-Webhook-Secret. |
activity | relation | — | The workflow this webhook triggers. |
active | boolean | true | Disable instead of deleting to keep history. |
event_type | string | — | Optional tag (e.g. form_submission). |
accept_files | boolean | true | Allow multipart/form-data uploads. |
max_file_size_mb | integer | 20 | Per-file cap, range 1–50. |
allowed_mime_types | json (array) | common doc types | Whitelist of MIME types for uploads. |
auto_enable_rag | boolean | false | Auto-index uploaded files into the Knowledge Base. |
allowed_origins | json (array) | [] | Empty = accept any origin; populated = request Origin/Referer must match (hostname or *.partner.com). |
previous_secret_key | string (private) | — | Old secret kept during the rotation grace window. |
previous_secret_expires_at | datetime | +24h on rotation | Grace window end (WEBHOOK_ROTATION_GRACE_HOURS to override). |
trigger_count / last_triggered_at | integer / datetime | — | Read-only usage stats. |
Webhook default file size is 20 MB
The schema default for max_file_size_mb is 20 (range 1–50). Set it per webhook to match the largest
file you expect; oversize uploads return 400 File upload failed.
Scheduled (api::workflow-schedule.workflow-schedule)
| Field | Type | Default | Notes |
|---|---|---|---|
name | string | — | Required. |
activity | relation | — | Required. Workflow to run. |
cron_expression | string | — | Required. Standard 5-field cron (see examples). |
timezone | string | UTC | IANA name, e.g. America/New_York, Asia/Kolkata. |
is_active | boolean | true | This is the pause switch — set false to pause. |
skip_if_running | boolean | true | Skip a tick if the previous run is still going (prevents overlap). |
inputs | json | — | Default inputs passed to the workflow each run. |
last_run_at / next_run_at | datetime | — | Read-only; next_run_at is the predicted next fire. |
last_status | enum | never_run | success · failed · running · queued · never_run. |
run_count / failure_count | integer | 0 | Read-only counters. |
Event source (api::event-source.event-source)
| Field | Type | Default | Notes |
|---|---|---|---|
name | string | — | Required. |
source_type | enum | — | Required. google_drive · webhook · email · slack. |
config | json | — | Source-specific configuration. |
drive_item_id | string | — | Drive folder/file ID (for google_drive). |
drive_item_type | enum | — | file or folder. |
drive_item_name | string | — | Display name of the watched item. |
watch_channel_id | string | — | Google watch channel ID (set on files.watch()). |
watch_resource_id | string | — | Google watch resource ID. |
watch_expiry | datetime | — | When the Drive watch expires — auto-renewed before this. |
page_token | string | — | Drive changes-API token for incremental sync. |
is_active | boolean | true | Pause without deleting the watch record. |
last_event_at / event_count | datetime / integer | — | Read-only stats. |
error_message | text | — | Last failure (e.g. expired/revoked watch). |
Event subscription (api::event-subscription.event-subscription)
| Field | Type | Default | Notes |
|---|---|---|---|
name | string | — | Human-readable name. |
event_source | relation | — | The source to subscribe to. |
activity | relation | — | The workflow to trigger when events match. |
event_types | json (array) | ["*"] | e.g. ["file_created", "file_modified"] or ["*"] for all. |
filter | json | — | Optional match criteria, e.g. { "mime_type": "application/pdf" }. |
input_mapping | json | — | How to map the event payload onto workflow inputs. |
is_active | boolean | true | Toggle the subscription on/off. |
trigger_count / last_triggered_at | integer / datetime | — | Read-only stats. |
Dependencies
- Event receiver endpoints in DMS (
/api/event-bus/*,/api/webhooks/trigger/:id). - Google Drive Watch API (for Drive event sources) + OAuth, plus a verified HTTPS domain for push.
- Job router / Redis + processors to execute the triggered workflow.
- Knowledge Base / RAG when
auto_enable_ragis on — uploaded files are indexed there.
Limitations
- The unified Event Bus is an architecture/future-release design; Google Drive watching is the first
source. Email and Slack sources are coming soon (the
source_typeenum already lists them). - Webhook file uploads are bounded by
max_file_size_mb(1–50) andallowed_mime_types. - Google Drive watches expire (~24h) and are auto-renewed when expiry nears; a revoked OAuth grant stops renewal.
- Webhook payload size and request rate are capped (see the developer webhook guide).
Tips & best practices
- Choose the trigger by who knows about the event. External system knows → webhook. The clock knows → schedule. A Drive folder knows → event source.
- Renewals are automatic, but watch
watch_expiry. A scheduler renews Drive watches before they lapse. Iferror_messageis set orlast_event_atis stale, re-authorize the source. - Make workflows idempotent. Drive can deliver the same change more than once, and webhook callers may
retry. Key your downstream writes on
file_id/event_idso a duplicate trigger is a no-op. - Filter at the subscription, not in the workflow. A
filterlike{ "mime_type": "application/pdf" }or narrowingevent_typesto["file_created"]avoids spinning up runs you'll just discard. - Rotate webhook secrets without downtime. Rotation keeps
previous_secret_keyvalid for the grace window so callers can deploy the new secret before the old one expires. - Pin origins for browser-triggered webhooks. Set
allowed_originsto your domains to reject calls from anywhere else. - Pause, don't delete. Set
is_active: false(schedule/source/subscription) oractive: false(webhook) to keep counters and history.
Concrete examples
Cron syntax
Standard 5-field cron: minute hour day-of-month month day-of-week.
| Goal | Cron |
|---|---|
| Every day at 9:00 AM | 0 9 * * * |
| Every Monday at 9:00 AM | 0 9 * * 1 |
| Every 15 minutes | */15 * * * * |
| Top of every hour | 0 * * * * |
| 9 AM on the 15th of each month | 0 9 15 * * |
| Weekdays at 6:30 PM | 30 18 * * 1-5 |
| First day of the month, midnight | 0 0 1 * * |
Always pair the cron with a timezone, e.g. Asia/Kolkata, so "9 AM" means 9 AM there — not UTC.
Webhook trigger payload + injected file variables
Trigger with a JSON body or multipart/form-data:
curl -X POST https://apisandbox.turfai.in/api/webhooks/trigger/1 \
-H "X-Webhook-Secret: your_64_char_secret" \
-F "resume=@candidate_resume.pdf" \
-F "applicant_name=Jane Smith" \
-F "email=jane@example.com"Each uploaded file (field name resume) is injected into workflow inputs:
inputs.resume_document_id // 789
inputs.resume_file_url // "gs://turfdms/uploads/..."
inputs.applicant_name // "Jane Smith" (plain form fields pass through)
inputs._documents.resume // { field_name, document_id, file_url, file_name, mime_type, file_size }
inputs._uploaded_files // array of all uploaded filesPattern: for a field named {field}, you get {field}_document_id and {field}_file_url, plus the
structured _documents.{field} and the _uploaded_files array. The response also returns an
execution_id and a short-lived polling_token.
Drive event source + subscription
# 1. Create the source (auto-registers a Drive watch)
curl -X POST https://apisandbox.turfai.in/api/event-sources \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "Invoices Folder", "source_type": "google_drive",
"drive_item_id": "folder_123", "drive_item_type": "folder" }'
# 2. Bind a workflow, filtered to new PDFs
curl -X POST https://apisandbox.turfai.in/api/event-subscriptions \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "PDF invoices", "event_source": 5, "activity": 123,
"event_types": ["file_created"],
"filter": { "mime_type": "application/pdf" } }'Injected event inputs (Drive)
When a Drive event fires, the bound workflow receives:
{
trigger_type: "event",
event_id: "evt_abc123",
event_source: "google_drive",
event_type: "file_created",
file_id: "abc123",
file_name: "invoice.pdf",
file_mime_type: "application/pdf",
folder_id: "folder_123"
}Reference these in nodes as {{file_id}}, {{file_name}}, {{file_mime_type}}, {{folder_id}},
and {{trigger_type}}.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Webhook returns 401 | Missing/mismatched secret | Send X-Webhook-Secret exactly; rotate and update the caller if unsure. |
| Webhook returns 403 | Webhook is active: false | Re-enable the webhook. |
| Webhook returns 400 on upload | File exceeds max_file_size_mb or MIME not in allowed_mime_types | Raise the limit (≤50) or add the MIME type; confirm accept_files is true. |
| Webhook call rejected by origin | allowed_origins set and request origin not listed | Add the caller's hostname/*.subdomain or clear allowed_origins. |
| Scheduled job never runs | is_active: false (paused) | Set is_active: true. |
| Schedule "skips" a run | Previous run still going + skip_if_running: true | Expected; let it finish or set skip_if_running: false to allow overlap. |
| Schedule fires at the wrong time | timezone defaulting to UTC | Set the IANA timezone; verify against next_run_at. |
| Drive events stop arriving | watch_expiry passed / OAuth revoked | Check error_message; re-authorize the source so renewal resumes. |
| Drive file lands but no run | Subscription filter / event_types don't match | Loosen filter (e.g. drop mime_type) or include the right event_type; check the event log for subscriptions_matched: 0. |
Execution stuck in queued | Job router / Redis down | Verify the job router and Redis are up. |
How to test
Webhook
POST /api/webhookswith anactivityid; copy the returnedurlandsecret_key.POSTto that URL withX-Webhook-Secretand a JSON (or form-data) body — confirm a200withexecution_idandpolling_token.- Poll
GET /api/workflow-executions/:idwith the polling token untilstatus: completed.
Schedule
- Create a schedule with a near-future cron (e.g.
*/2 * * * *) and the correcttimezone. - Confirm
next_run_atis populated, then wait forlast_run_atto update andrun_countto increment. - Pause with
is_active: falseand confirm no further runs.
Drive event source + subscription
- Create a
google_driveevent source for a folder you own; confirmwatch_channel_id/watch_expiryare set. - Create a subscription binding a workflow,
event_types: ["file_created"]. - Drop a file into the folder; confirm an event-log entry (
subscriptions_matched: 1) and a triggered run with the injectedfile_id/file_nameinputs.
APIs used
| Method | Path | Purpose |
|---|---|---|
POST | /api/webhooks | Create a webhook |
POST | /api/webhooks/trigger/:id | Trigger a workflow (public, secret-auth) |
POST | /api/webhooks/:id/regenerate-secret | Rotate the webhook secret |
POST | /api/event-bus/drive-webhook | Drive push receiver |
POST | /api/event-sources | Create event source (auto-registers watch) |
POST | /api/event-subscriptions | Bind a workflow to a source |
GET | /api/event-logs | View event history / audit trail |
GET | /api/workflow-executions/:id | Poll execution status |
Related
- Visual Workflow Builder — add a trigger node to a workflow canvas.
- Integrations — Google Drive OAuth and other connectors that back event sources.
Roadmap: Google Drive event sources are live today. Email and Slack event sources are coming soon — the same subscription + filter model will apply.