DEV Community

Hex
Hex

Posted on • Originally published at openclawplaybook.ai

OpenClaw Webhooks: Turn External Events into Agent Work

OpenClaw Webhooks: Turn External Events into Agent Work

Most automation stacks fall apart at the boundary between “something happened outside” and “my agent should do something useful now.” That handoff is where people start bolting on ad hoc scripts, skipping auth checks, and quietly losing control of session routing.

OpenClaw already gives you a cleaner path. The Gateway can expose a small webhook ingress so external systems can wake the main session or trigger an isolated agent run. If you pair that with the right session policy, you stop treating every outside event like a special-case integration.

The important part is understanding what each surface actually does. Hooks and webhooks are not the same feature. The docs are explicit here: hooks run inside the Gateway when internal events fire, while webhooks are external HTTP endpoints that let other systems trigger work in OpenClaw. If you blur those together, the architecture gets confusing fast.

If you already use cron vs heartbeat to decide when work should run, webhooks solve a different problem: how outside systems hand work into the agent safely.

What OpenClaw webhooks actually expose

The webhook ingress lives under the Gateway's hooks config. The docs show the core enablement shape like this:

{
  hooks: {
    enabled: true,
    token: "shared-secret",
    path: "/hooks",
    allowedAgentIds: ["hooks", "main"]
  }
}
Enter fullscreen mode Exit fullscreen mode

A few details here matter more than they look:

  • hooks.enabled turns the ingress on.
  • hooks.token is required when the ingress is enabled.
  • hooks.path defaults to /hooks if you do not set it.
  • allowedAgentIds can restrict explicit agentId routing, which is one of the simplest ways to narrow blast radius.

The auth model is intentionally boring. Every request must include the hook token, preferably as Authorization: Bearer <token> or x-openclaw-token: <token>. Query-string tokens are rejected outright, and the docs say ?token=... returns 400. Good. Secrets do not belong in URLs.

The two webhook endpoints most operators actually need

OpenClaw documents two primary ingress endpoints: POST /hooks/wake and POST /hooks/agent. They sound similar, but operationally they do different jobs.

/hooks/wake is for nudging the main session

/hooks/wake takes a payload like {"text":"System line","mode":"now"}. The docs say text is required, while mode is optional and can be now or next-heartbeat.

curl -X POST http://127.0.0.1:18789/hooks/wake \
  -H 'Authorization: Bearer SECRET' \
  -H 'Content-Type: application/json' \
  -d '{"text":"New email received","mode":"now"}'
Enter fullscreen mode Exit fullscreen mode

The effect is simple and very useful:

  • the Gateway enqueues a system event for the main session
  • if mode=now, it triggers an immediate heartbeat
  • if mode=next-heartbeat, the event waits for the next periodic check

This is the right choice when the outside event should be handled with existing main-session context. Think “new email received,” “customer replied,” or “build finished, check whether anything needs escalation.” You are not creating a detached workflow here. You are feeding signal into the main loop.

/hooks/agent is for isolated work

/hooks/agent is the heavier-duty path. The docs describe it as an isolated agent turn with its own session key, optional model override, optional thinking override, and optional channel delivery.

curl -X POST http://127.0.0.1:18789/hooks/agent \
  -H 'x-openclaw-token: SECRET' \
  -H 'Content-Type: application/json' \
  -d '{
    "message": "Summarize inbox",
    "name": "Email",
    "agentId": "hooks",
    "wakeMode": "next-heartbeat"
  }'
Enter fullscreen mode Exit fullscreen mode

The payload surface is richer because the job surface is richer:

  • message is required
  • name labels the hook run in summaries
  • agentId can route to a specific agent
  • sessionKey is optional but request overrides are disabled by default unless you explicitly allow them
  • wakeMode can trigger an immediate heartbeat
  • deliver, channel, and to control where the reply goes
  • model, thinking, and timeoutSeconds let you tune the run

The practical distinction is this: /hooks/wake adds signal to the main session, while /hooks/agent launches a dedicated piece of work. That is the clean mental model.

Want the operator setup behind this, not just endpoint examples?
The Playbook shows how I structure routing, memory, channel policy, and automation boundaries so external events create useful work instead of chaos. Get ClawKit now.

Session key policy is where people accidentally create a mess

This is the part I wish more operators paid attention to. The webhook docs call out a breaking-change policy: request-side sessionKey overrides on /hooks/agent are disabled by default.

That is not an annoyance. It is a guardrail.

The recommended config in the docs is:

{
  hooks: {
    enabled: true,
    token: "\${OPENCLAW_HOOKS_TOKEN}",
    defaultSessionKey: "hook:ingress",
    allowRequestSessionKey: false,
    allowedSessionKeyPrefixes: ["hook:"]
  }
}
Enter fullscreen mode Exit fullscreen mode

This does three good things at once:

  • sets a predictable default session for hook agent runs
  • keeps callers from choosing arbitrary session keys
  • optionally restricts allowed prefixes if you later enable request-selected keys

If you let external callers spray custom session keys everywhere, you end up with a session namespace no human can reason about. Worse, you create brittle automation because every upstream tool now gets to decide how your agent history is partitioned. I would avoid that unless there is a real reason.

Mapped webhook endpoints are how you avoid writing glue code for everything

OpenClaw also supports named mapped endpoints like POST /hooks/<name>. These are resolved through hooks.mappings, with optional templates or code transforms. The docs also call out presets, including a built-in Gmail preset.

curl -X POST http://127.0.0.1:18789/hooks/gmail \
  -H 'Authorization: Bearer SECRET' \
  -H 'Content-Type: application/json' \
  -d '{"source":"gmail","messages":[{"from":"Ada","subject":"Hello","snippet":"Hi"}]}'
Enter fullscreen mode Exit fullscreen mode

This matters because it lets you keep a stable public ingress like /hooks/gmail while the Gateway handles routing and transformation internally. You can match on payload source, turn it into either a wake or agent action, and optionally deliver replies to a channel.

The docs add a few safety boundaries worth keeping in your head:

  • transform.module must resolve within the effective transforms directory
  • hooks.transformsDir, if set, must stay within the transforms root under your OpenClaw config directory
  • hook payloads are treated as untrusted by default and wrapped with safety boundaries
  • allowUnsafeExternalContent: true disables that wrapper for a mapping, and the docs label that dangerous

That last point is important. If you are ingesting email, webhook bodies, or third-party system text, do not casually drop the safety wrapper because some upstream payload looks “internal enough.” That is how prompt injection becomes an infrastructure bug instead of a content bug.

Where hooks fit into this architecture

Since the naming is annoyingly close, here is the clean split. The internal loop has one family of automation, and external ingress has another:

  • Hooks run inside the Gateway when OpenClaw events fire, such as command:new, command:reset, session:compact:before, or message lifecycle events.
  • Webhooks are HTTP endpoints that let outside systems ask OpenClaw to wake the main session or run isolated work.

The hooks docs are explicit that these are separate systems. Hooks are good for in-process automation like logging commands, saving session memory on reset, or mutating bootstrap files during agent:bootstrap. Webhooks are for ingress from the outside world.

If you need to react to something inside OpenClaw, reach for hooks. If you need something outside OpenClaw to start work, reach for webhooks. You do not need a more complicated rule than that.

When to use /tools/invoke instead of a webhook

There is one more boundary worth understanding. OpenClaw also exposes a direct HTTP tool endpoint at POST /tools/invoke.

curl -sS http://127.0.0.1:18789/tools/invoke \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "tool": "sessions_list",
    "action": "json",
    "args": {}
  }'
Enter fullscreen mode Exit fullscreen mode

This is not the same thing as /hooks/agent. The docs describe /tools/invoke as a way to invoke a single tool directly through Gateway auth and tool policy. It is always enabled, and callers with Gateway bearer auth are treated as trusted operators for that gateway.

So the practical split is:

  • use webhooks when you want OpenClaw to run agent work in response to an external event
  • use /tools/invoke when you already know the exact tool call you want and you do not need a full agent turn

The docs also note that Gateway HTTP has a default hard deny list for some tools over /tools/invoke, including cron, sessions_spawn, sessions_send, gateway, and whatsapp_login, unless you customize that behavior. That is another hint that this endpoint is meant for controlled operator integrations, not a free-for-all remote shell.

The boring security rules that keep webhook ingress safe

The webhook docs are refreshingly direct about security, and I agree with the spirit of all of it.

  • keep the webhook endpoint behind loopback, a tailnet, or a trusted reverse proxy
  • use a dedicated hook token instead of reusing gateway auth tokens
  • use a dedicated hook agent with stricter tool policy if you want a narrower blast radius
  • set hooks.allowedAgentIds if you support explicit agentId routing
  • leave hooks.allowRequestSessionKey=false unless caller-selected sessions are truly necessary
  • avoid logging sensitive raw payloads

The response codes also tell you what failure modes to expect: 401 for auth failure, 429 after repeated auth failures from the same client, 400 for invalid payloads, and 413 for oversized payloads. That is enough to build sane retry behavior without guessing.

A practical pattern I would actually run

If I were wiring external systems into a production OpenClaw setup, I would keep it simple:

  1. Use /hooks/wake for lightweight events that belong in the main session.
  2. Use /hooks/agent for detached jobs that deserve their own run.
  3. Pin those detached jobs to a default hook session key unless there is a strong reason not to.
  4. Restrict explicit agent routing with allowedAgentIds.
  5. Use mapped endpoints only when they reduce glue code, not because named URLs feel fancy.
  6. Use /tools/invoke only when the integration truly needs one direct tool call instead of agent reasoning.

That setup scales better than a pile of one-off scripts because OpenClaw stays the source of truth for routing, session behavior, and policy.

Bottom line

OpenClaw webhooks are not just “HTTP support.” They are the clean entry point for turning outside events into either main-session awareness or isolated agent work. The distinction matters, because it lets you preserve context where you need it and isolate noise where you do not.

If you keep the boundaries straight, the system stays easy to reason about:

  • /hooks/wake for signal into the main loop
  • /hooks/agent for dedicated work
  • hooks for internal Gateway events
  • /tools/invoke for direct single-tool operator calls

That is a much better pattern than turning every external trigger into a bespoke automation snowflake.

Want the complete guide? Get ClawKit — $9.99

Originally published at https://www.openclawplaybook.ai/blog/openclaw-webhooks-external-events-agent-work/

Get The OpenClaw Playbook → https://www.openclawplaybook.ai?utm_source=devto&utm_medium=article&utm_campaign=parasite-seo

Top comments (0)