hooks.*.
For the full key index and the other top-level config domains, see Configuration reference.
Hooks
hooks.* configures generic Gateway HTTP ingress. For setup and a verified first
request, see Webhooks. This is separate from
internal hooks (hooks.internal, HOOK.md) and the
TaskFlow Webhooks plugin (plugins.entries.webhooks).
main with the intended configured agent. Hook tokens grant ingress
access, not an authenticated sender identity; treat payload content as untrusted
data and restrict the target agent’s tools and workspace separately.
hooks.token should be distinct from active Gateway shared-secret auth
(gateway.auth.token / OPENCLAW_GATEWAY_TOKEN or gateway.auth.password /
OPENCLAW_GATEWAY_PASSWORD). Startup logs a non-fatal warning on reuse;
openclaw security audit reports a critical finding, including password auth
supplied at audit time (--auth password --password <password>). Use
openclaw doctor --fix to rotate a persisted reused hook token, then update all
external senders.
Hook HTTP contract
Paths below assumehooks.path: "/hooks"; replace that prefix if configured
differently. Send POST with a JSON body and
Content-Type: application/json.
Authentication accepts Authorization: Bearer <token> or x-openclaw-token.
A nonempty Bearer token takes precedence. A token query parameter is rejected
with 400, even if a valid header is also present. Missing or wrong credentials
return 401. After 20 failed attempts in a 60-second window, further invalid
authentication attempts from that client are throttled with 429 and
Retry-After; valid authentication resets the counter. Loopback is not exempt.
Configure trusted proxy attribution correctly before exposing a proxy route.
The normal body limit is 256 KiB, with a 30-second body-read timeout.
Gmail-path mappings receive a larger derived allowance described below. Generic
hooks parse JSON but do not require the JSON content-type header; the TaskFlow
plugin does enforce it.
The direct
/wake and /agent endpoints take precedence over mappings with
those names. /hooks itself has no action.
Agent admission failures use
{ ok: false, error, runId? }. Early method/auth/path
failures can be plain text; do not assume every error response is JSON. The
15-second admission deadline is separate from the body-read timeout and
timeoutSeconds for the agent turn. HTTP success does not prove a model result
or channel delivery. See hook verification.
Hook agent payload
Omitting all destination fields runs without a direct announce destination.
Supplying only part of a destination fails with
400 while delivery is enabled.
deliver: false disables announcement, not the agent’s ability to use messaging
tools; constrain those tools in the agent policy when needed.
Hook session and agent policy
Direct request agent ids must exist. Mapping agent ids resolve to a configured agent, with the legacy default-agent fallback for unknown mapping ids. If no owner can be resolved, admission fails rather than inventing an agent. The effective agent must passallowedAgentIds; global session-store ownership is
also enforced. Agent-prefixed keys are re-scoped to the selected agent and
prefix-checked again.
Keys resolve from the request/mapping, then hooks.defaultSessionKey, then a
generated hook:<uuid>. A configured default must match the prefix allowlist.
Without a default, the allowlist must admit generated hook: keys.
- Direct
/agentpersistent mode requires an explicit requestsessionKey,allowRequestSessionKey: true, and a nonempty prefix allowlist. - Persistent mappings require a stable mapping
sessionKeyordefaultSessionKey. Static mapping keys do not require caller-key opt-in, but still obey configured prefixes. - Templated mapping keys require a nonempty prefix allowlist at configuration resolution and
allowRequestSessionKey: trueat dispatch. This includes the built-in Gmail preset unless an earlier mapping overrides it. /wakeaccepts an explicit key only withmode: "now"and the same caller-key/prefix policy. Without one, it uses the selected agent’s main session;defaultSessionKeyis for agent runs, not wakes.
defaultSessionKey therefore orders those requests but can make a later
single request hit the admission timeout while an earlier run is still active.
Mapping details
Custommappings run in array order before presets. The first match owns the
request, including a transform that returns null; later mappings are not tried.
Both match predicates must pass when supplied. Omitting them matches any custom
hook path.
Templates support
{{payload.field}} or {{field}}, array indexing such as
{{messages[0].subject}}, {{headers.x-event-type}}, {{query.kind}}, {{path}},
and {{now}} (ISO timestamp). Missing/null values become empty strings; objects
serialize as JSON. An empty rendered session-key template is rejected.
Transforms receive { payload, headers, url, path } and may return a partial
action override, asynchronously if needed. Action output uses kind: "agent" or
"wake", with message or text respectively. Returning null skips the action;
when no actions remain the response is 204, before any run, task, execution
identity, or audit receipt is created. Transform exceptions return 500.
A transform-provided sessionKey is externally derived by default. Only trusted
code producing a fixed key should mark sessionKeySource: "static"; never use
that marker to bypass policy for a payload-derived key. Transforms execute as
trusted Gateway code, not in the reader agent’s sandbox. They are cached until
hook configuration reload. Keep modules under the hooks transforms root, not
workspace skill directories; move invalid modules there or remove an invalid
transformsDir if doctor reports it.
Hook retries and fan-out
Agent replay keys resolve in this order:Idempotency-Key,
X-OpenClaw-Idempotency-Key, then payload idempotencyKey. Only trimmed nonempty
strings of at most 256 characters are used. The same key replays only for the
same token, path, and resolved dispatch fields; changing the message or routing
can create a new run. Pending admissions and admitted runs with unresolved
completion are retained without TTL or size eviction. After terminal completion
settles, its replay entry expires after 5 minutes and counts toward the 1,000
terminal-entry memory bound. Restart clears all replay state. Failed admissions
remain retryable. Direct retries may change waitForCompletion without changing
dispatch identity: admission-only callers replay the runId, while waiting
callers share the same completion promise and replay its terminal result.
When requested, completion.status is ok, error, or skipped, and
replyDisposition is visible, silent, or empty. This disposition exposes
only whether a terminal model reply existed, never its text. The optional
delivery fields are delivered, deliveryAttempted, deliveryError, and
deliverySuppressionReason (empty, silent, heartbeat, or
channel_transform). Missing delivery fields remain unknown. Post-admission
failures still return HTTP 200; only admission failures use the non-2xx
statuses above. deliveryError, when present, is the fixed categorical value
"delivery-failed". Provider, runtime, model, target, session, diagnostic,
output, and summary details are never returned.
For forEach, templates/transforms see the original payload with the chosen
array replaced by [currentItem]. Missing, empty, or non-array values produce no
actions (204). Only the first 200 items are processed; excess items are
dropped with a warning, not an HTTP failure. Split larger batches at the sender.
Fan-out agent dispatch waits up to 8 seconds after mapping/transform work.
Pending admissions continue in the background without the single-run 15-second
cancellation deadline. A fully admitted multi-agent batch returns:
{ ok: true, runId }. Partial failures or
pending items return non-2xx with ok: false, an incomplete-batch error, admitted
runIds, and up to five failure messages in errors. A pending-only batch uses
503. An error can therefore coexist with admitted or still-pending work.
Agent fan-out derives replay identity from each rendered action even without an
explicit idempotency key. Identical retries reconcile pending/admitted items
within the cache lifetime; keep transforms deterministic for retries. Wake
actions dispatch immediately and have no replay identity, including mixed
wake/agent batches. Their response includes eventOutcome: "queued" if any wake
was accepted by its queue, or "coalesced" if every wake was coalesced by its queue.
This is not durable exactly-once processing.
Gmail integration
The Gmail preset routes/hooks/gmail through forEach: "messages" and
sessionKey: "hook:gmail:{{messages[0].id}}", with isolated mode by default. A
custom matching mapping runs before the preset. Without a mapping agentId, the
preset uses the resolved default agent; conversation isolation does not restrict
that agent’s tools or workspace.
Apply the restricted Gmail reader
configuration
before connecting untrusted mail. The setup command configures transport, not the
reader or session-key policy. For the templated key, set
allowRequestSessionKey: true and allowedSessionKeyPrefixes: ["hook:gmail:"]
with a matching defaultSessionKey, or allow the broader "hook:" namespace.
To keep caller-key overrides disabled, replace the preset with an earlier mapping
using a static sessionKey. Keep isolated mode unless context reuse is intended.
Gmail-path mappings use a request-body allowance of
max(256 KiB, min(32 MiB, 100 × (3 × maxBytes + 8192))). The multiplier reserves
space for escaped content and message metadata; it is not a guarantee that every
upstream backlog fits. The upstream history page size counts history records,
which can contain multiple messages. Fan-out still processes only the first 200
items and logs dropped excess. See batch limits and
retries.
When hooks.enabled: true and hooks.gmail.account is set, the Gateway starts
gog gmail watch serve if its executable and required transport configuration
are available, and renews the watch. Set OPENCLAW_SKIP_GMAIL_WATCHER=1 to opt out.
Do not start a second foreground watcher on the same listener. Setup output can
contain tokens; see the CLI reference.
A successful push or hook response is transport/admission evidence, not proof of
completed email processing or delivery. Verify the restricted reader through
logs and its run output. For a
reader-to-agent handoff, expose only the required tool and constrain the
default-on tools.agentToAgent
policy with allow, or set enabled: false when no handoff is needed; see also
Prompt injection and
per-agent sandbox and tools.