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
7.1 KiB
summary, read_when, title
| summary | read_when | title | ||
|---|---|---|---|---|
| Webhooks plugin: authenticated TaskFlow ingress for trusted external automation |
|
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.
Related
- Hooks - internal event-driven hooks vs. this HTTP-based TaskFlow bridge
- Gateway webhooks (
hooks.*config) - separate generic Gateway HTTP endpoint feature; not the same as this plugin's routes - Plugin runtime SDK
- CLI webhooks