create_flow creates a tracking record; run_task creates or links a child task
record. Neither operation starts an agent. The external controller owns the
workflow and advances its state.
To submit an agent turn from an external event, use Gateway HTTP
hooks. To react to internal agent events, use
internal hooks. Those surfaces do not share this plugin’s routes or authentication.
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 underplugins.entries.webhooks.config:
secret accepts a plain string or a SecretRef: { source: "env" | "file" | "exec" | "store", provider: "default", id: "..." }.
SecretRefs resolve into the Gateway’s startup config snapshot. When one route’s
secret cannot resolve, the Gateway keeps running and that exact route stays
registered but cold: requests receive a generic authentication failure (401).
Other routes remain available. Fix the SecretRef source, then reload or restart
the Gateway to activate the new snapshot. SecretRef values are never resolved
on the public request path.
Security model
Each route authenticates with its ownsecret, not hooks.token or the Gateway
auth token. Use HTTPS outside loopback. The endpoint accepts the action schema
below, not arbitrary provider webhook payloads, URL verification challenges, or
provider-specific HMAC signatures. Use an existing automation service to validate
and translate external events when needed. Treat event content as data.
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.
POST method, fixed-window rate limit, JSON content
type, in-flight limit, shared-secret authentication, bounded JSON body read, then
action validation. Earlier failures do not reach later checks.
Request format
SendPOST requests with Content-Type: application/json and either
Authorization: Bearer <secret> or x-openclaw-webhook-secret: <secret>:
result.created: true and result.flow with
its flowId, revision (initially 0), and status (default queued). Keep the
flow id and read it back using the same route:
200 confirms a read or record operation, not completed agent work or
message delivery. Use get_task_summary for linked task counts and
task inspection for actual task status and delivery status. Keep
those outcomes separate from the controller’s flow status.
Supported actions
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. Read result.current
or call get_flow, reconcile the change, then use its current revision.
cancel_flow and run_task do not take expectedRevision. Action schemas reject
unknown fields.
Read actions can return HTTP 200 with flow: null or summary: null when the
flow is absent or outside the route’s session. A successful read does not imply
that a matching flow exists.
create_flow
goal is required. Optional fields are controllerId, status (queued,
running, waiting, blocked), notifyPolicy (done_only, state_changes,
silent), currentStep, stateJson, and waitJson. The request’s controllerId
overrides the route default; it is not a separate authorization boundary.
Creation has no general idempotency key: retrying create_flow after an uncertain
connection result can create a second flow. Reconcile with list_flows before
repeating creation. Child success alone does not mark a managed flow finished;
the controller must advance or finish the flow when appropriate.
run_task
Required fields are flowId, runtime, and task. Allowed runtime values are
subagent and acp; status defaults to queued and can be running. startedAt, lastEventAt, and
progressSummary are only valid when status is "running"; sending them
with any other status returns 400 invalid_request.
The following links an already existing, currently owned backing run. Use
the managed flow id returned by create_flow, and the childSessionKey and
runId from that existing run. Inventing a child key or run id does not start
work or grant authority.
childSessionKey requires the exact runId and current backing ownership by the
route’s configured session. Foreign, stale, or replaced runs are rejected at use
time. Omit childSessionKey to create an unbacked tracking record; supplying an
invalid backing reference is rejected. Reuse by
runId is scoped to the runtime, owner, child, and flow; it is not a universal
request-replay guarantee.
Optional metadata includes sourceId, parentTaskId, agentId, label,
preferMetadata, and notifyPolicy (done_only, state_changes, silent).
startedAt and lastEventAt are nonnegative integer timestamps in milliseconds.
Waiting and completion
set_waiting accepts currentStep, stateJson, waitJson, blockedTaskId, and
blockedSummary. A nonempty blocked field selects blocked; otherwise the flow
becomes waiting. resume_flow accepts status (queued default or running),
currentStep, and stateJson, and clears waiting/blocked state.
finish_flow marks the flow succeeded and accepts stateJson; fail_flow marks
it failed and also accepts blockedTaskId and blockedSummary. Optional string
fields accept null to clear them; stateJson and waitJson accept any JSON
value, including retained JSON null. Use the current expectedRevision for
each transition.
Cancellation
request_cancel records cancellation intent. cancel_flow attempts cancellation
of linked work. If children remain active, the response is HTTP 202 with
ok: true, code: "cancel_pending", and result.cancelled: false. Check
get_flow and get_task_summary afterward; 202 does not mean cancellation
finished.
Response shape
ownerKey,
requesterSessionKey, and requesterOrigin. Task views can still include
childSessionKey, agentId, and runId; treat responses as operational data.
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.
Errors and troubleshooting
Failures before action validation can be plain text, not the JSON envelope
above. A Bearer header takes precedence over
x-openclaw-webhook-secret;
query-string and body tokens are not authentication methods for this plugin.
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