Files
openclaw-openclaw/docs/plugins/webhooks.md
Peter Steinberger f7d7148cf0 docs: rewrite published docs grounded in current source (#100142)
Source-grounded rewrite of 529 published docs pages with per-unit information-loss verification: 1,713 factual corrections cited to src/**, generated surfaces regenerated, frontmatter titles preserved for i18n, release notes pages untouched. All docs gates green.

Closes #100141
2026-07-05 00:32:47 -04:00

7.1 KiB

summary, read_when, title
summary read_when title
Webhooks plugin: authenticated TaskFlow ingress for trusted external automation
You want to trigger or drive TaskFlows from an external system
You are configuring the bundled webhooks plugin
Webhooks plugin

The Webhooks plugin adds authenticated HTTP routes so a trusted external system (Zapier, n8n, a CI job, an internal service) can create and drive managed OpenClaw TaskFlows over HTTP, without writing a custom plugin.

The plugin runs inside the Gateway process. For a remote Gateway, install and configure it on that host, then restart the Gateway. It ships with no routes configured, so it is a no-op until you add at least one route.

Configure routes

Set config under plugins.entries.webhooks.config:

{
  plugins: {
    entries: {
      webhooks: {
        enabled: true,
        config: {
          routes: {
            zapier: {
              path: "/plugins/webhooks/zapier",
              sessionKey: "agent:main:main",
              secret: {
                source: "env",
                provider: "default",
                id: "OPENCLAW_WEBHOOK_SECRET",
              },
              controllerId: "webhooks/zapier",
              description: "Zapier TaskFlow bridge",
            },
          },
        },
      },
    },
  },
}

Route fields:

Field Required Default Notes
enabled no true
path no /plugins/webhooks/<routeId> Must be unique across routes.
sessionKey yes - Session that owns the bound TaskFlows.
secret yes - Plain string or a SecretRef (below).
controllerId no webhooks/<routeId> Used as the default create_flow controller.
description no - Operator note only.

secret accepts a plain string or a SecretRef: { source: "env" | "file" | "exec", provider: "default", id: "..." }.

Every configured route registers at startup regardless of whether its secret currently resolves. An unresolvable secret does not disable or skip the route - requests to it fail authentication (401) until the secret can be resolved. SecretRef values are re-resolved on every request, so rotating the underlying secret (env var, file, or exec output) takes effect without a Gateway restart.

Security model

Each route acts with the TaskFlow authority of its configured sessionKey: it can inspect and mutate any TaskFlow owned by that session. TaskFlow access always goes through api.runtime.tasks.managedFlows.bindSession(...), so a route can never act outside its bound session. To limit blast radius:

  • Use a strong, unique secret per route.
  • Prefer a SecretRef over an inline plaintext secret.
  • Bind routes to the narrowest session that fits the workflow.
  • Expose only the specific webhook path you need.

Request handling order for each path: HTTP method (POST only) and Content-Type: application/json checks, then fixed-window rate limiting (120 requests per 60-second window per path+client-IP key, up to 4,096 tracked keys), then in-flight request limiting (8 concurrent requests per key, up to 4,096 tracked keys), then shared-secret authentication, then a 256 KB / 15-second JSON body read. Requests that fail an earlier check never reach later ones.

Request format

Send POST requests with Content-Type: application/json and either Authorization: Bearer <secret> or x-openclaw-webhook-secret: <secret>:

curl -X POST https://gateway.example.com/plugins/webhooks/zapier \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_SHARED_SECRET' \
  -d '{"action":"create_flow","goal":"Review inbound queue"}'

Supported actions

Action Purpose
create_flow Create a managed TaskFlow for the route's session.
get_flow Fetch one TaskFlow by id.
list_flows List TaskFlows for the route's session.
find_latest_flow Fetch the most recently updated TaskFlow.
resolve_flow Resolve a TaskFlow by opaque token.
get_task_summary Fetch the task summary for a TaskFlow.
set_waiting Mark a TaskFlow waiting, with optional state/wait data.
resume_flow Resume a waiting/blocked TaskFlow.
finish_flow Mark a TaskFlow finished.
fail_flow Mark a TaskFlow failed.
request_cancel Request cooperative cancellation.
cancel_flow Cancel a TaskFlow (may return 202 if children are still active).
run_task Create a managed child task inside an existing TaskFlow.

Mutating actions (set_waiting, resume_flow, finish_flow, fail_flow, request_cancel) require flowId and expectedRevision for optimistic concurrency; a stale revision returns 409 revision_conflict.

create_flow

{
  "action": "create_flow",
  "goal": "Review inbound queue",
  "status": "queued",
  "notifyPolicy": "done_only"
}

run_task

Allowed runtime values: subagent, acp. startedAt, lastEventAt, and progressSummary are only valid when status is "running"; sending them with any other status returns 400 invalid_request.

{
  "action": "run_task",
  "flowId": "flow_123",
  "runtime": "acp",
  "childSessionKey": "agent:main:acp:worker",
  "task": "Inspect the next message batch"
}

Response shape

{
  "ok": true,
  "routeId": "zapier",
  "result": {}
}
{
  "ok": false,
  "routeId": "zapier",
  "code": "not_found",
  "error": "TaskFlow not found.",
  "result": {}
}

Flow and task views never include owner/session metadata, so responses cannot leak the route's bound sessionKey. code values include not_found, not_managed, revision_conflict, persist_failed, cancel_requested, cancel_pending, terminal, invalid_request, request_rejected, and action-specific fallback codes (mutation_rejected, create_rejected, task_not_created, cancel_rejected) when a mutation is rejected for a reason not covered by the named codes above.