Triggers

How integrations start - webhooks, schedules, manual

This is for integration engineers building workflows. Before you can configure the rest of a workflow, you need to answer one question: what makes this workflow run? That's what a trigger is. Every workflow has exactly one.

There are three answers, and the right choice usually determines a lot about the rest of the design:

  • A Webhook trigger when an external system tells you something happened — an order was placed, a customer was created, a file was uploaded. You give the external system a URL; it posts to that URL; your workflow runs.
  • A Schedule trigger when something needs to happen on a clock — nightly imports, hourly syncs, weekly reports. You write a cron expression; the platform fires the workflow at those times.
  • A Manual trigger when a human in Cockpit (or a programmatic call) kicks the workflow off — one-off operations, debugging, retrying a known failure.

The rest of this page goes through each one in detail with configuration examples.

Webhook Trigger

A webhook trigger turns your workflow into an HTTP endpoint and runs it whenever a request arrives. It's the right choice any time the outside world needs to push something to you the moment it happens: an order created, a customer registered, a payment confirmed. That covers most event-driven and real-time integrations, including the webhooks that third parties like Stripe and Shopify emit — you hand them a URL, and they call it when there's news.

That URL follows a fixed shape, with your workflow's ID at the end:

text
https://integrations.revenexx.com/webhooks/{workflow_id}

You give this URL to the external system, and it POSTs data to it. A request from the calling system looks like an ordinary HTTP POST — a JSON body, and usually an auth header:

Bash
curl -X POST https://integrations.revenexx.com/webhooks/workflow-123 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer secret_xyz" \
  -d '{
    "order_id": "12345",
    "customer_id": "cust-456",
    "total_amount": 1500.00,
    "items": [...]
  }'

On your side, the trigger node itself is small — the path it listens on and the method it accepts:

JSON
{
  "connector": "webhook-trigger",
  "config": {
    "path": "/orders/received",
    "method": "POST"
  }
}

Once the workflow starts, everything from that incoming request is available to later steps as the trigger's output: {{steps.0.outputs.body}} holds the JSON payload, {{steps.0.outputs.headers}} the HTTP headers (where you'll find things like Authorization and X-Signature), and {{steps.0.outputs.query}} any query-string parameters.

Securing a webhook

Because a webhook URL is reachable by anyone who learns it, you should assume it will be probed and verify that each request is genuinely from the system you expect. There are two common defences, and they layer well together. The stronger one is signature verification: the caller signs the request body with a shared secret and sends the result in an X-Signature header, and an early step in your workflow recomputes the HMAC and compares — {{steps.0.outputs.headers['X-Signature']}} == HMAC-SHA256(body, secret). The simpler one is an API key: require a known token in the Authorization: Bearer <key> header and reject anything that doesn't match, again as early in the workflow as possible so unauthenticated calls do no work.

What a failure returns

How a failed run is reported depends on the engine. On an async workflow the caller gets a 202 Accepted the instant the request is received — the actual work happens in the background, and the durable engine handles any retries, so the caller never sees the failure directly. On a sync workflow the caller is held until the work finishes, so a failure comes straight back as a 500. That difference matters when you're deciding how the calling system should behave: an async webhook should be fire-and-forget, while a sync one needs the caller to handle errors.

Schedule Trigger

A schedule trigger runs a workflow on a recurring clock rather than in response to an event. You reach for it whenever the work is driven by time instead of by something happening elsewhere: the nightly product-catalog sync, the daily sales summary pushed to the ERP, the Sunday-night job that purges old records, the hourly inventory audit. If the sentence describing the job contains the word "every," it's almost certainly a schedule.

The timing is expressed as a cron expression — five fields covering minute, hour, day of month, month, and day of week:

Cron Expression Syntax:

text
┌─────────────────── minute (0 - 59)
│ ┌───────────────── hour (0 - 23)
│ │ ┌─────────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
│ │ │ │ ┌─────────── day of week (0 - 7, 0 and 7 are Sunday, or SUN-SAT)
│ │ │ │ │
│ │ │ │ │
* * * * *

If you don't write cron expressions every day, these cover most of what you'll need:

text
0 9 * * *              # Every day at 9:00 AM UTC
0 */6 * * *            # Every 6 hours (0:00, 6:00, 12:00, 18:00)
0 2 * * 0              # Every Sunday at 2:00 AM UTC
30 18 * * MON-FRI      # Every weekday at 6:30 PM UTC
0 0 1 * *              # First day of every month at midnight

The trigger node pairs the expression with a timezone, which matters more than it looks: a "2 AM" import means a very different moment in Zurich than in UTC, and getting it wrong can shift a nightly job across a day boundary.

JSON
{
  "connector": "schedule-trigger",
  "config": {
    "schedule": "0 */6 * * *",
    "timezone": "Europe/Zurich"
  }
}

Timezones default to UTC, with the common business zones supported explicitly:

  • UTC (default)
  • Europe/Zurich, Europe/Berlin, Europe/London
  • America/New_York, America/Los_Angeles
  • Asia/Tokyo, Asia/Singapore
  • Australia/Sydney

A scheduled workflow does not run the moment you deploy it — its first execution is the next clock time that matches the expression. When it does run, the trigger hands the workflow a small object describing the firing, so downstream steps can reason about when they were invoked:

JSON
{
  "scheduled_time": "2025-05-26T18:00:00Z",
  "timezone": "Europe/Zurich",
  "next_execution": "2025-05-27T00:00:00Z"
}

One guarantee is worth designing around: schedules are at-least-once. The platform promises the job will run, but if the system recovers from a failure it may run the same firing twice. So any workflow that writes data — especially one with database steps — should be idempotent, meaning a second run with the same inputs produces the same end state rather than duplicate records. A processed_at marker or a dedupe on an event ID is the usual way to get there.

You can watch all of this in Cockpit under Integration Studio → Workflows → Workflow Name, which shows each run's time, status, and — when something went wrong — the error detail.

Manual Trigger

A manual trigger puts the start button in a person's hands: it adds a "Sync Now" control in Cockpit that a customer or operator clicks to run the workflow, optionally filling in a few parameters first. It's for the work that isn't event-driven or scheduled but happens on demand — a one-off "import now," testing and debugging a workflow as you build it, overriding an automatic schedule when something urgent comes up, or any user-requested operation like generating a report or retrying a payment.

You declare the inputs you want to collect, and Cockpit renders a form for them:

JSON
{
  "connector": "manual-trigger",
  "config": {
    "parameters": [
      {
        "name": "order_id",
        "type": "string",
        "required": true,
        "description": "Order ID to resync"
      },
      {
        "name": "force_retry",
        "type": "boolean",
        "required": false,
        "description": "Skip cache and retry all steps"
      },
      {
        "name": "custom_mapping",
        "type": "json",
        "required": false,
        "description": "Override field mappings"
      }
    ]
  }
}

Each parameter has a type, which determines the form control Cockpit renders for it:

  • string — Text input
  • number — Integer or float
  • boolean — Checkbox
  • json — JSON object (advanced)
  • select — Dropdown (add options: ["option1", "option2"])

When the operator submits the form, the workflow receives their values as the trigger's output, alongside who ran it and when — useful for auditing who kicked off a given run:

JSON
{
  "order_id": "12345",
  "force_retry": true,
  "triggered_by": "user_abc@example.com",
  "triggered_at": "2025-05-26T18:30:00Z"
}

In Cockpit the workflow shows a Run button with that parameter form. After it's pressed, a sync workflow streams its status back live, while an async one returns an execution ID you can follow in the run history.

Trigger Comparison

FeatureWebhookScheduleManual
Triggered byExternal systemTimeUser click
Async mode
Sync mode
ParametersHTTP bodyCron timingForm fields
Typical latency<100ms±1 min<100ms
Use caseReal-time eventsBatch/recurringOne-off/debug

Best Practices

A few habits make trigger-based workflows easier to live with.

With webhooks, treat every request as untrusted. Validate each one with a signature, an API key, or both, and do it before the workflow does any real work. Log the incoming calls so that when a partner says "we sent it," you can confirm what actually arrived — and if your workflow calls external APIs in response, wrap those calls in exponential backoff so a brief outage on their side doesn't turn into a failed run on yours.

With schedules, design for the run happening twice. Because firing is at-least-once, a workflow that isn't idempotent will eventually create duplicates. Make the same inputs produce the same end state — lean on a processed_at timestamp or an event ID to detect and skip work you've already done — and set timeouts that comfortably fit a long import so a slow upstream doesn't get cut off mid-run.

With manual triggers, use them to de-risk the automated ones. Run a workflow by hand a few times before you put it on a schedule, give parameter fields sensible defaults so an operator can't easily misfire it, and log which trigger started a run. When you're debugging later, knowing whether a run came from a webhook, the schedule, or a person's click is often the fastest way to the cause.

Next Steps

Was this page helpful?