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 event record.
  • 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 (v2 suffix) 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:

  1. Signature verification—HMAC-SHA256 using the token secret. GitHub, Stripe-style.
  2. Schema validation—matches the event definition above.
  3. Idempotency check—based on event.id header 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 dead in Redis after final failure; you can replay selectively.

Real-world wiring patterns

Sentry alerts → candidate bug fixes

The workflow most teams start with:

  1. Sentry project → Integrations → Generic Webhook → paste your /hooks/:token.
  2. Filter to level:error on Sentry’s side.
  3. 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.
  4. A second trigger watches for linear.ticket.closed events 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 pm2 or 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 .env unless 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/logs endpoint (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 tunnelnpx openclaw tunnel 3000 spins up an ngrok-style HTTPS URL hitting your laptop for debugging GitHub webhooks.
  • Metrics – Prometheus exporter at /metrics; Grafana dashboard JSON in extras/.

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 via cron.
  • 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.