OpenClaw lives and dies by the quality of its events. If you can’t get a signal from the outside world into the agent, you’re back to writing shell scripts. This guide goes deep into the OpenClaw webhook and event-driven automation architecture: how the gateway exposes endpoints, how events are typed and validated, how triggers map to agent actions, and how teams are wiring Sentry errors, GitHub activity, and email receipts straight into production agents.
Why event-driven matters for OpenClaw
OpenClaw agents already know how to browse, shell out, and call 800+ SaaS APIs via Composio. But most useful work starts with an external event—an error in Sentry, a pull-request opened in GitHub, or a customer email. Polling is wasteful and slow. Webhooks give you millisecond-level reaction time and lower CPU on the daemon.
Internally, OpenClaw follows the classic publish/subscribe model:
- Gateway (HTTP) receives the webhook, writes an
eventrecord. - Event bus (Redis Streams or local Node queue) fans out to subscribed triggers.
- Scheduler resolves the trigger to an agent
task. - Daemon executes the task, persists memory, emits follow-up events.
The separation keeps your automation logic version-controlled and replayable. Roll back code without losing events—the queue persists.
Anatomy of an OpenClaw webhook surface
The gateway exposes a single root /hooks/:token. A token isolates environments—dev, staging, prod. Under the hood it’s an Express 5 route (Node 22+). You can create tokens via the CLI or UI:
npx openclaw hooks:create --name prod-alerts --scopes sentry,github,email
This writes to ~/.openclaw/config.json:
{
"hooks": {
"prod-alerts": {
"token": "u3jh8P4...",
"scopes": ["sentry", "github", "email"],
"verifySignature": true,
"enabled": true
}
}
}
On ClawCloud the same thing lives in the Gateway → Webhooks tab; the UI spits out a URL like
https://my-team.claw.cloud/hooks/u3jh8P4
All incoming HTTP methods are accepted but 99 % of integrations use POST. The gateway never stores raw bodies longer than necessary—just enough for retry and auditing—then discards or redacts secrets.
Declaring event types and schemas
OpenClaw does not impose a fixed schema, but you’ll thank yourself later if you type events. The recommended place is openclaw.config.js:
export const events = [
{
name: "sentry.error",
source: "sentry",
schema: {
id: "string",
project: "string",
level: "string",
culprit: "string",
url: "string"
}
},
{
name: "github.pull_request.opened",
source: "github",
schema: {
number: "number",
repo: "string",
author: "string",
branch: "string",
url: "string"
}
},
{
name: "email.received",
source: "imap",
schema: {
from: "string",
subject: "string",
body: "string",
messageId: "string"
}
}
];
On boot the daemon compiles these to zod validators. Bad payloads are rejected with HTTP 400 and never reach the queue, avoiding dead-letter noise.
Versioning tips
- Bump the event name (
v2suffix) when fields break. - Use additive changes for non-breaking updates—OpenClaw ignores unknown keys by default.
- Archive old consumers instead of hot-patching.
Ingest pipeline: validation, auth, replay
Once the request lands, three middlewares run, in this order:
- Signature verification—HMAC-SHA256 using the token secret. GitHub, Stripe-style.
- Schema validation—matches the event definition above.
- Idempotency check—based on
event.idheader or computed hash. Duplicate submissions (common with Sentry retries) are 200-OK but ignored.
Valid events are pushed into the bus with a 30-day retention cap. Replay is a single CLI call:
npx openclaw events:replay --since "2024-05-01T00:00:00Z" --type sentry.error
Useful when your action code panics and you hotfix.
Mapping triggers to agent actions
Triggers live in triggers/*.ts. Think of them as if/then rules but in real code so you get tests and type safety. Minimal example:
// triggers/autoFix.ts
import { when } from "@openclaw/sdk";
export default when("sentry.error", async ({ event, agent }) => {
if (event.level !== "error") return;
await agent.shell.run(`grep -R "${event.culprit}" src | head -n 5`);
await agent.memory.save(`error:${event.id}`, event);
await agent.chat.send({
channel: "devops",
text: `🚨 Sentry error ${event.culprit} in ${event.project}`
});
});
At runtime, the trigger registers a filter (sentry.error) on the bus. The moment an event hits, the agent receives a task object containing the payload and convenience helpers (browser, shell, Composio, etc.).
Error handling
- Throwing inside the trigger rolls back memory writes and retries up to
MAX_RETRIES=5. - Use
agent.log.debug()liberally; ClawCloud streams logs to the UI. - Mark tasks
deadin Redis after final failure; you can replay selectively.
Real-world wiring patterns
Sentry alerts → candidate bug fixes
The workflow most teams start with:
- Sentry project → Integrations → Generic Webhook → paste your
/hooks/:token. - Filter to
level:erroron Sentry’s side. - OpenClaw trigger (above) greps the codebase, posts Slack message, and opens a Linear ticket via Composio if the same error appears three times in 15 minutes.
- A second trigger watches for
linear.ticket.closedevents and pushes feedback to Sentry via its REST API.
This closed loop took us from 40 % manual triage to 5 % in a week. The hardest part was stitching auth tokens across systems—store them in secrets/, encrypted with age.
GitHub events → automated PR reviews
GitHub App setup gives you better rate limits than a raw token. Permissions: Pull requests: Read-only, Contents: Read-only, Checks: Write.
App → Webhooks → set to pull_request events only, pointing to the same OpenClaw endpoint. Payload example:
{
"action": "opened",
"number": 42,
"repository": { "full_name": "acme/api" },
"pull_request": { "head": { "ref": "feature/login" }, "html_url": "..." }
}
Trigger:
// triggers/review.ts
export default when("github.pull_request.opened", async ({ event, agent }) => {
const diff = await agent.github.getDiff(event.repo, event.number);
const comments = await agent.ai.reviewCode(diff, {
styleGuide: "google",
maxComments: 10
});
await agent.github.commentPR(event.repo, event.number, comments);
await agent.github.setStatus(event.repo, event.sha, {
state: "success",
description: `${comments.length} suggestions posted by OpenClaw`
});
});
The ai.reviewCode helper is a thin wrapper around OpenAI 4o; swap for Anthropic on-prem if policy demands.
Email receipt → task creation in Notion
Email integration is less obvious because IMAP/POP is pull-based. We solved it with AWS SES inbound → Lambda → OpenClaw webhook. Simpler if you’re on Google Workspace: Apps Script HTTP POST to the webhook on message create.
// triggers/emailToTask.ts
export default when("email.received", async ({ event, agent }) => {
const task = await agent.notion.createPage({
databaseId: process.env.NOTION_TASK_DB!,
properties: {
Name: { title: [{ text: { content: event.subject } }] },
From: { rich_text: [{ text: { content: event.from } }] }
},
children: [
{ object: "block", type: "paragraph", paragraph: { text: [{ text: { content: event.body } }] } }
]
});
await agent.chat.send({ channel: "ops", text: `✉️ New email task: ${task.url}` });
});
Edge cases: massive attachments. Filter them in Lambda (if size > 5 MB drop) or you’ll choke the gateway.
Running on ClawCloud vs self-hosted
The webhook code is identical, but there are operational differences:
- Scaling – ClawCloud auto-scales worker nodes based on queue depth. Self-hosters need to wire
pm2or Kubernetes HPAs. - Public reachability – On prem you fight NAT; ClawCloud gives you an SSL endpoint out of the box with Let’s Encrypt.
- Replay limits – ClawCloud keeps 90 days of events; OSS default is 30, tunable via
EVENT_TTL_DAYS. - Secrets – Hosted offering integrates with AWS KMS; local mode reads
.envunless you override with--vault.
I still run a tiny self-host in homelab for side projects, but production lives on ClawCloud for the SLOs.
Observability and debugging
Nothing burns HN karma faster than a black-box automation that silently eats events. Key tooling:
- Audit log –
/gateway/logsendpoint (or Logs tab) shows every 2xx and 4xx with latency, body hash, and event ID. - Replay viewer – UI lets you click an event and see which triggers fired, their exit codes, duration, stdout/stderr.
- Local tunnel –
npx openclaw tunnel 3000spins up an ngrok-style HTTPS URL hitting your laptop for debugging GitHub webhooks. - Metrics – Prometheus exporter at
/metrics; Grafana dashboard JSON inextras/.
Common pitfall: pushing giant payloads (>1 MB) from GitHub checks. The gateway will 413; trim your payload or bump MAX_BODY_BYTES.
Security and rate limiting
Webhooks are an ingress surface; treat them like you treat login pages.
- IP allow list – Configure
hooks[ipAllow]array. GitHub ranges update hourly viacron. - HMAC secrets – Rotate quarterly; Sentry supports multiple active secrets for painless rollover.
- Rate limit – Defaults to 100 req/s per token. Bursty build systems can spike; keep Jenkins on its own token.
- Backpressure – If queue lag >5 min the gateway flips to 429; upstream retriers back off.
For compliance heads: Events in ClawCloud are encrypted at rest (AES-256) and deleted on TTL expiry; audit logs keep hashes only.
Practical next step
If you already have OpenClaw running, create a token and wire a single external event—Sentry, GitHub, or email—end to end. Keep the trigger logic minimal (console.log the payload) until you see stable delivery. Once events flow, layering agent actions is trivial. The whole stack ships in the openclaw repo—read the source; half the value is understanding exactly what your automation platform is doing under the hood.