# 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](https://www.openclawplaybook.ai/blog/openclaw-cron-vs-heartbeat-automation-loop/) 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"]
  }
}
```

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"}'
```

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"
  }'
```

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](https://www.openclawplaybook.ai?utm_source=hashnode&utm_medium=article&utm_campaign=parasite-seo).

## 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:"]
  }
}
```

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"}]}'
```

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](https://www.openclawplaybook.ai/blog/openclaw-heartbeat-proactive-ai-agent/) 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": {}
  }'
```

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](https://www.openclawplaybook.ai?utm_source=hashnode&utm_medium=article&utm_campaign=parasite-seo)

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

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

