Webhooks
Each webhook step gets its own endpoint verified by bearer token or HMAC (GitHub, Stripe, plain). Covers immediate, last-step and respond-step modes.
The developer-facing contract behind the webhook trigger. Each webhook step gets its own endpoint:
POST https://<your-instance>/webhooks/<webhook-id>
Authentication
Two verification methods, set on the step:
- Bearer: Coworkly mints the token; send it as
Authorization: Bearer …. Rotating the token on the step invalidates the old one immediately. - HMAC: for senders that sign payloads. Formats:
github(X-Hub-Signature-256),stripe(Stripe-Signature), orplain(hex digest of the body; custom header name supported).
Unauthenticated deliveries get 401 and never touch a workflow.
Response modes
| mode | behavior | caller sees |
|---|---|---|
| immediate | ack now, run async | 202 {"ok": true, "execution_id": …} |
| last step | block until the run ends | 200 + the terminal step's output |
| respond step | block until a respond to webhook step fires | that step's status/headers/body; the rest of the run continues in background |
Other codes worth handling: 404 means an unknown id or the workflow isn't active (deliberately indistinguishable); 500 means the run failed before responding; 504 means the response timeout elapsed (the workflow keeps running; only the HTTP wait gave up).
Sample capture
While a webhook step is capturing, one
authenticated delivery is accepted even if the workflow isn't
active. It returns 202 {"ok": true, "captured": true} and
records the payload shape without running anything.
App events (platform-routed)
Each event arrives normalized: payload.text, user, channel,
channel_type, ts, thread_ts, mentioned (the bot was
@-mentioned or DM'd) and bot_thread (a reply in a thread the bot is
already part of, so a conversation continues without re-mentioning
it). Gate on mentioned or bot_thread for an assistant.
A reaction (event_type: "reaction_added") adds reaction (the emoji
name), item_user (who wrote the reacted-to message) and item_type;
its channel, ts and thread_ts point at that message, so a step
can reply in its thread or read it back. The bot's own reactions never
come back as events.
The webhook step's App event source is a different transport with
the same trigger: instead of a per-step URL, a published Coworkly
app (Slack first) receives every event for the connected account
at one platform endpoint, POST /webhooks/inbound/:provider, and
Coworkly finds the connections authorized for that account (team_id
for Slack, the installation for GitHub, organizationId for
Linear), and in each connection's own workspace fires only the
triggers bound to that connection. Nothing is exposed per workflow, so there is no URL or
secret to manage.
Each provider delivery is signature-verified with the app's own
secret before parsing (fail-closed), deduplicated by the provider's
event id, and, for Slack and GitHub, filtered through a bot-loop
guard so the bot can never trigger itself. Linear shows a workflow's
changes as the connected person's, so a Linear workflow that edits
its own trigger issue guards on payload.changed instead. The same endpoint family serves the platform SMS
and email triggers (/inbound/surge, /inbound/resend).
Delivery semantics
Coworkly doesn't re-request a failed inbound delivery; retries are
the sender's job (most webhook senders retry on non-2xx). Once a
delivery is accepted (202), execution retries happen internally.
The endpoint is rate-limited per source IP.