Workflow triggers — open webhook decisions
Status: idea. The framing is settled in durable-workflows § Triggers — one door, not a plane: triggers aren’t a plugin plane, they’re callers of one door (wf_spawn(id, payload)). This note only holds the decisions the inbound-webhook door still forces — read that section first.
Goal
Let a workflow author declare an external caller: a stable signed URL that an outside system (GitHub, Linear) hits to spawn a run, its body becoming the payload. Schedule (built) and manual spawn (built) already cover the other callers. The webhook is the one seam left.
Settled (don’t relitigate)
- No
triggers[]list, noapikind. Manual spawn with a payload (POST /api/workflows, bot-key authed) already is the API trigger — a second per-workflow token would duplicate the house key. Schedule stays its own column + pg_cron mirror; don’t migrate it for symmetry. - Payload is pass-through. The body lands in payload untouched; recipes shape it with
{{path}}, rendered in the conductor, unresolved paths fail early. No ingest-time mapping DSL.
Open decisions (the webhook door forces these)
- Storage. The webhook caller needs a row, not a jsonb element: stable id (the URL embeds it),
enabled, rotate/revoke, a rate cap,last_fired_at. You can’t cleanly disable the Nth element of an array. - Signing secret is trigger-owned, not a house secret. House secrets are name-unique, sandbox-injected, member-readable — a signing secret is none of those. Reuse Vault storage, verify server-side like
x-conductor-secret/hashApiKey; don’t put it in thesecretsnamespace. - Idempotency. Webhooks retry — derive Absurd’s idempotency key from the delivery id (
X-GitHub-Delivery) so a redelivery doesn’t double-spawn.wf_spawntakes no key today, so this shapes its signature now. - Why-a-run-fired. Record trigger ref + raw inbound body on the run so
arbe wf showcan say “fired from the GitHub webhook at 04:02 with this body.”
Ship-gate
Per-trigger rate cap is a gate, not a caution: the webhook door doesn’t ship enabled without a count window in the trigger row returning 429 past it. With no external users, the real failure is our own runaway loop, and one hit = one sandbox run = real money.