Webhooks

A webhook in Catentio is an inbound thing: a gate in a pipeline that blocks until an external system calls back. The pipeline waits; your system tells it to go.

That direction matters, so state it plainly:

Outbound webhooks now exist — see Webhooks (outbound). This page is the inbound gate feature, which is a different thing: a pipeline step that blocks until an external system calls back.

The /v1/webhooks namespace has exactly one endpoint: GET /v1/webhooks, which lists webhook gates.

What a webhook gate is

A pipeline workflow declares a step as a gate with gate_kind: webhook:

{
  "step_slug": "await_render_farm",
  "kind": "gate",
  "gate_kind": "webhook"
}

When the runner reaches that step, its subtask goes blocked and nothing downstream of it runs. It stays blocked — indefinitely — until something outside Catentio resolves it. That's the whole feature: a pipeline pause that an external system controls.

webhook is one of four gate kinds (human, webhook, schedule, pipeline_dep). Human gates are the fully shipped surface with portal Approve/Reject buttons; webhook gates are resolved programmatically, as described below.

List webhook gates

GET /v1/webhooks

Returns every subtask with kind = gate and gate_kind = webhook — both the ones currently waiting and the recently resolved history. Ordering puts waiting gates first, then most-recent activity.

Query params:

Param Type Notes
status string Filter to one subtask status. A gate that is currently waiting is blocked. Full set: pending, running, blocked, review, succeeded, failed, skipped, stale.
limit int (1–1000) Default 200.

Response:

{
  "data": [
    {
      "subtask_id": "9f3c1b0e-5d2a-4c77-9a1e-6b0f2d8c4a11",
      "project_id": "spring-lookbook-a1b2c3d4",
      "project_title": "Spring lookbook",
      "workflow_slug": "content",
      "phase_slug": "render",
      "step_slug": "await_render_farm",
      "task_id": "1c7e8a44-2f90-4b3d-8e15-7a6c9d0b2e33",
      "status": "blocked",
      "gate_config": {},
      "consumes_artifacts": [],
      "consumes_subtasks": [],
      "created_at": "2026-05-12T10:42:00.000Z",
      "started_at": "2026-05-12T10:42:00.512Z",
      "completed_at": null
    }
  ],
  "meta": {
    "total": 1,
    "by_status": [{ "status": "blocked", "count": 1 }]
  }
}
Field Type Notes
subtask_id string (uuid) The gate subtask. This is what you pass when resolving it.
project_id string Project id — a title slug plus a hex suffix (no prj_ prefix).
project_title string Project title.
workflow_slug string The workflow the project was built from.
phase_slug string The phase (task) the gate sits in.
step_slug string The gate step's slug, as declared in the workflow.
task_id string (uuid) The parent task (phase).
status string Subtask status — blocked while waiting.
gate_config object The gate's config from the workflow. {} when unset.
consumes_artifacts array Artifact slugs the gate consumes.
consumes_subtasks array Sibling step slugs the gate consumes.
created_at timestamp ISO 8601.
started_at timestamp | null When the runner reached the gate.
completed_at timestamp | null When it resolved. null while waiting.

meta.by_status is a count of all webhook gates by status, not just the ones in data.

const { data } = await catentio.webhooks.list();
gates = catentio.webhooks.list()
curl 'https://catent.io/api/v1/cp/v1/webhooks?status=blocked' \
  -H "Cookie: catentio_session=<session cookie set at login>"

This is the only endpoint in the namespace. There is no POST /v1/webhooks and no DELETE /v1/webhooks/{id} — see A note on the SDK surface.

Resolving a gate

A webhook gate is resolved through the runtime tool pipeline_resolve_webhook_gate, which writes the gate's output and wakes the project back to active so the runner picks it up.

It is a SENSITIVE, agent-invoked tool, not a public REST route. There is no unauthenticated POST /hooks/... URL that an arbitrary third party can hit; the callback path runs through an agent or an internal handler that invokes the tool (an HTTP route you own, a Discord relay, a GitHub webhook handler, and so on).

Params:

Param Type Notes
project_id string Required.
subtask_id string Required. The gate subtask id from GET /v1/webhooks.
decision string approved or rejected. Defaults to approved — webhook callers almost always approve; rejected is for the rare case where the external system declines.
decided_by string Free-form source identifier, max 64 chars. Defaults to webhook. E.g. github-webhook, stripe-webhook. Recorded in the event log and the human_decision.v1 payload.
payload object | null Optional free-form payload from the external system. Kept on the gate's output under payload.
comment string | null Optional.

The tool rejects the call if the subtask doesn't exist, isn't a gate, or isn't gate_kind = webhook.

Portal

Dashboard → Webhooks (/dashboard/webhooks) renders this same list — what's currently blocked on an external callback, plus the recently resolved history. It's a read-only inspection view.

Looking for outbound webhooks?

They exist, and they are a different feature on a different surface: Webhooks (outbound), on the public API. Register a URL, and Catentio POSTs to it when a run or project finishes — HMAC-signed, retried with backoff.

This page is the inbound gate feature: a pipeline step that blocks until an external system calls back to release it. The two are easy to confuse and point in opposite directions.

The runtime also writes internal project_events rows (topics like project_started, project_completed — underscored, not dotted). Those are database rows for audit and observability, read with GET /v1/events; they are a separate, larger set from the three deliverable webhook events.

A note on the SDK surface

The SDKs expose webhooks.create() and webhooks.delete() alongside webhooks.list(). Only list() works. create() and delete() target POST /v1/webhooks and DELETE /v1/webhooks/{id}, which the control plane does not implement — calling them fails. They are a leftover of the outbound surface described above and should not be used.

Errors

Status When
401 Missing or invalid credential.
502 CP couldn't reach the runtime to fetch gate rows.

Next

  • Gates & approvals — all four gate kinds, and the human-approval flow.
  • Events — the queryable event archive (the pull channel; there is no push channel).
  • Projects — the pipelines these gates live in.