Blocks

Webhook

Get a unique URL external systems POST to, authenticated by bearer token or HMAC. Covers the payload.body envelope, response modes, and Slack app events.

The workhorse trigger: Coworkly mints a unique URL, an external system POSTs JSON to it, the workflow runs.

When it runs

On every authenticated request to the trigger URL (shown in the Inspector once the step exists). Requests authenticate with the step's bearer token (or HMAC signing; pick in the Inspector). Rotate from there if a secret leaks. The workflow must be active for the URL to fire.

Config

The webhook trigger's Inspector config Response mode, and the live URL + token.

  • Response mode: what the caller gets back.
    • immediate: answers 202 at once; the run continues in the background. The default.
    • last_step: the response waits for the run and returns the final step's output (with a poll interval + timeout).
    • respond_step: a respond to webhook step somewhere in the graph crafts the response explicitly. This is the way to build synchronous APIs.

Output

The envelope, the single most important shape in Coworkly:

{
  "body":    { …the caller's parsed JSON… },
  "headers": { …request headers… },
  "query":   { …query-string params… }
}

Headers that carry a credential (authorization, cookie, any signature, token, secret or API-key header, and the trigger's own signature header) arrive as "[redacted]". Coworkly checks them before the run starts and never stores their values, so a run's history can't leak the caller's token. Everything else (content-type, x-github-event, and so on) is there to read.

The caller's data is under payload.body.*, never top-level: {{ payload.body.order_id }}, and in code steps p.body.order_id. Referencing payload.order_id is the classic first webhook bug. The build-time linter flags it, but know why.

Patterns

  • Receive → validate → route: a code step checks required fields, an if gates invalid payloads out.
  • Synchronous API: respond_step mode + a respond-to-webhook step returning computed JSON.
  • Testing: the Inspector's Test tab opens a sample window; curl the URL and the real request becomes the step's pinned sample.

App events (no URL)

The webhook step has a second Source: App event. Pick a connected app (Slack, GitHub or Linear) and the account (which Slack workspace, GitHub installation or Linear organization), and the workflow starts from that account's own events instead of an HTTP call. Only the account you pick reaches the workflow: another Slack team connected to the same Coworkly workspace, another Linear organization, or a teammate's private connection never does. With a single connection the account is picked for you; with several, the trigger waits until you choose.

The Events field picks which of the app's events start a run: an event for any action (issue, pull_request) or one action (issue.create, pull_request.opened). An event you didn't pick never creates a run, so it costs nothing and stays out of the run history. Leave it empty to take every event and branch downstream.

The webhook step in App-event mode Source → App event → Slack. The URL, auth, and response fields disappear; there's nothing to configure.

How it works: Coworkly's published Slack app delivers every event for your Slack workspace to the platform, and it's routed to your Coworkly workspace by the account it belongs to. Connect Slack in Settings, invite the bot to the channels it should hear, and you're live. No URLs, no secrets, no dashboard setup on your side.

With no Events picked, the workflow receives every event the app delivers: messages, @-mentions, emoji reactions, DMs. Narrow with the Events field, or downstream with a switch on payload.event_type or an AI classify:

{ "event_type": "app_mention",
  "text": "<@U0BOT> is google workspace down?",
  "user": "U2147483697",
  "channel": "C0123ABCDEF",
  "channel_type": "channel",
  "ts": "1749600000.000200",
  "thread_ts": "1749600000.000200",
  "team_id": "T0123ABCDEF",
  "mentioned": true,
  "raw": { "…": "the full Slack event" } }

Reply in-thread by passing payload.thread_ts to the Slack send step. Two guarantees the platform enforces before anything runs: the bot's own posts (and other bots) never start a run, and an @-mention, which Slack delivers twice, arrives once.

Testing is capture-real: press Run, then @-mention the bot in Slack; that real event both runs the draft and pins the step's sample.

Linear events

Pick Linear and the workflow runs on your Linear workspace's issues, comments, emoji reactions, projects, project updates and project labels as they are created, changed or removed. Branch on payload.event and payload.action:

{ "event": "issue",
  "action": "update",
  "issue_identifier": "ENG-42",
  "issue_title": "Checkout times out",
  "state": "In Progress",
  "state_type": "started",
  "assignee_name": "Ada Lovelace",
  "labels": ["Bug"],
  "changed": ["stateId"],
  "state_changed": true,
  "assignee_changed": false,
  "previous": { "stateId": "…" },
  "actor_name": "Ada Lovelace",
  "data": { "…": "the full Linear issue" },
  "raw": { "…": "the whole delivery" } }

A comment adds body and comment_id and carries its issue's issue_id, issue_identifier and issue_title; a reaction adds emoji. Linear shows changes a workflow makes through your Linear connection as yours, so they come back as events too. A workflow that updates the issue that triggered it should check payload.changed (for example, only continue when state_changed is true) so its own edit doesn't start it again.

If you connected Linear before events were available, reconnect it once in Settings so events start arriving.

Gotchas

  • Inactive workflow → the URL answers but doesn't fire. Activate first.
  • Non-JSON bodies arrive best-effort; JSON is the contract.