Configuration & CLI Gateway Config

OpenClaw Hooks Documentation: The Complete Builder Reference

Every event type. Every payload field. Every filter option. The hooks system is OpenClaw's integration backbone — this reference covers everything you need to wire up reliable, production-grade automations without gaps.

TC
T. Chen
AI Systems Engineer
Feb 11, 2025 16 min read 8.1k views
Updated Feb 11, 2025
Key Takeaways
  • Every hook entry requires: url (string), events (array of event type strings) — headers and filter are optional but strongly recommended
  • All payloads share a common envelope: event, timestamp, gatewayId — plus event-specific fields in the payload object
  • Use webhook.site or a local ngrok tunnel for testing before pointing hooks at production endpoints
  • Hook delivery timeout is 5 seconds — endpoints must respond immediately; process payloads asynchronously
  • Hooks fire in local dev too — always use separate hook URLs for development and production environments

The official OpenClaw docs give you the hook configuration skeleton. What they don't give you is the complete payload schema for every event type, the exact filter field names that work, or the debugging approach when hooks stop firing. That's what this reference covers — built from hands-on testing across a dozen production deployments.

Hook Configuration Schema

The hooks block in gateway.yaml is an array. Each entry is a hook registration. Here is the complete schema for a single hook entry:

hooks:
  - url: string                    # required — HTTPS or HTTP endpoint
    events:                        # required — list of event type strings
      - message.received
      - message.sent
    headers:                       # optional — sent with every delivery
      X-Secret: "token-value"
      Authorization: "Bearer xyz"
    filter:                        # optional — restrict delivery scope
      channelId: string            # match specific channel
      agentName: string            # match specific agent
      skillName: string            # match specific skill (skill.invoked only)
    timeout: 5                     # optional — seconds, default 5, max 30
    enabled: true                  # optional — set false to pause without removing

The enabled field is one that most builders miss. You can temporarily disable a hook without removing it from the config — useful when an endpoint is under maintenance or during debugging. Set enabled: false, restart the gateway, and hook delivery pauses for that entry only.

💡
Multiple Hooks, Same Event
You can have multiple hook entries listening for the same event type. The gateway fires all matching hooks concurrently. This lets you send agent.offline events to both Slack and PagerDuty simultaneously without combining the logic in one receiver.

Event Payload Reference

Every payload shares this envelope:

{
  "event": "event.type",
  "timestamp": "2025-02-11T09:14:33Z",
  "gatewayId": "gw-identifier",
  "version": "1"
}

Each event type adds event-specific fields inside a payload object. Here are the complete schemas:

message.received

{
  "event": "message.received",
  "timestamp": "...",
  "agentName": "assistant",
  "channelId": "telegram-main",
  "channelType": "telegram",
  "payload": {
    "messageId": "msg-1234",
    "text": "What's the weather?",
    "senderId": "user-5521",
    "senderUsername": "alice",
    "channelMeta": { "chat_id": 9988, "is_group": false }
  }
}

message.sent

{
  "event": "message.sent",
  "agentName": "assistant",
  "channelId": "telegram-main",
  "payload": {
    "messageId": "msg-1235",
    "text": "It's 18°C and clear in London.",
    "recipientId": "user-5521",
    "tokensUsed": 287,
    "modelUsed": "claude-sonnet-4-6",
    "latencyMs": 1240
  }
}

task.completed / task.failed

{
  "event": "task.completed",
  "agentName": "researcher",
  "payload": {
    "taskName": "daily-brief",
    "cronExpression": "0 8 * * *",
    "durationMs": 18400,
    "tokensUsed": 3120,
    "outputSummary": "Compiled 14 articles into daily brief"
  }
}

For task.failed, the payload adds errorType, errorMessage, and attempt (which retry attempt this was).

agent.online / agent.offline

{
  "event": "agent.offline",
  "agentName": "customer-support",
  "payload": {
    "consecutiveMisses": 3,
    "lastSeen": "2025-02-11T09:10:22Z",
    "reason": "heartbeat_timeout"
  }
}

skill.invoked

{
  "event": "skill.invoked",
  "agentName": "assistant",
  "payload": {
    "skillName": "web-search",
    "invocationId": "inv-8821",
    "inputSummary": "search query: OpenClaw hooks",
    "durationMs": 890
  }
}

Filter Options

Without a filter, a hook fires for all channels and agents. In a multi-channel deployment this generates enormous volume. The filter block narrows delivery scope:

filter:
  channelId: "slack-support"      # only Slack support channel events
  agentName: "triage-bot"         # only this specific agent
  skillName: "gmail"              # only when Gmail skill is invoked

Filters are AND conditions — if you specify both channelId and agentName, both must match. You cannot use OR logic within a single filter block. Register two separate hook entries to achieve OR behavior.

The skillName filter only applies to skill.invoked events. Specifying it on other event types is silently ignored — the filter is applied as if not set.

⚠️
channelId vs channelType
The filter uses channelId — the unique identifier you assigned to the channel in your channel configuration, not the channel type (telegram, slack, etc.). If you want to filter all events from any Telegram channel, you'd need to list each channel's ID or use separate hook entries per channel.

Testing Hook Delivery

The fastest way to test hooks during development: use webhook.site. It gives you a free temporary URL that captures every incoming request with headers and body. Point your hook at it, trigger events, and inspect the exact JSON your integration will receive.

# Set your hook URL to webhook.site's generated URL
hooks:
  - url: "https://webhook.site/your-unique-id"
    events: [message.received, task.completed]

For local development where you want to test against your actual receiver code, use ngrok to expose a local port:

ngrok http 8000
# Use the generated https URL as your hook endpoint

Trigger a test event by sending a message through any connected channel. Check webhook.site or your ngrok tunnel's request inspector. Confirm the payload structure matches what your receiver code expects before moving to production.

Delivery Guarantees and Timeouts

OpenClaw hooks are at-most-once delivery. The gateway attempts delivery once per event. If your endpoint returns an error or doesn't respond within the timeout, the failure is logged and the event is not retried.

The default timeout is 5 seconds. You can extend this to up to 30 seconds with the timeout field on each hook entry, but this holds a gateway thread for the duration. High-latency endpoints under load can create delivery backlog. The correct pattern: return 200 immediately, process asynchronously.

Here's a receiver that does this correctly with a background thread:

from flask import Flask, request
import threading

app = Flask(__name__)

def process_event(data):
    # Do the actual work here — database writes, API calls, etc.
    print(f"Processing: {data['event']}")

@app.route("/hook", methods=["POST"])
def hook():
    data = request.json
    t = threading.Thread(target=process_event, args=(data,))
    t.daemon = True
    t.start()
    return "", 200   # Return immediately, don't wait for processing

Common Mistakes

Not returning 200 immediately. If your receiver does a database write before returning, and the database is slow, you'll hit the 5-second timeout. The gateway marks the hook as failed. The event is gone. Always return 200 first, then process.

Using the same hook URL in development and production. Every message you send during local testing hits your production monitoring system. This creates alert noise and potentially incorrect metrics. Use separate URLs per environment — the enabled field makes it easy to maintain both configs.

Not logging failed hook deliveries. The gateway logs hook failures at WARNING level. If you're not watching logs, failed hooks are invisible. Set up a log alert for "hook delivery failed" so you know when your integration is breaking without user-visible failures.

Frequently Asked Questions

Where is the official OpenClaw hooks documentation?

The official docs are in the OpenClaw GitHub repository under docs/hooks.md. This reference guide expands on the official docs with complete payload schemas, filter nuances, and integration patterns that the raw reference doesn't cover — particularly the testing workflow and delivery guarantee details.

What does the message.received payload look like?

It includes: event type, timestamp, gatewayId, agentName, channelId, channelType, and a payload object with messageId, the user's message text, senderId, senderUsername, and channel-specific metadata like Telegram's chat_id or Slack's thread_ts. All string fields, no nested objects beyond channelMeta.

How do I test hook delivery without a live endpoint?

Use webhook.site for a free temporary capture URL. Set it as your hook endpoint, trigger any event, and inspect the exact payload structure. For testing against your actual receiver, use ngrok to expose a local port over HTTPS — the gateway requires HTTPS for production endpoints.

Can I send hooks to multiple URLs for the same event?

Yes. Register multiple hook entries with the same event types but different URLs. The gateway fires all matching hooks concurrently per event. There's no hard limit on hook entries, but each adds delivery overhead — keep the list focused on integrations you actively use.

What timeout does OpenClaw use when delivering hooks?

The default timeout is 5 seconds, configurable up to 30 per hook entry via the timeout field. Endpoints must respond within this window. Design receivers to return HTTP 200 immediately and process the payload asynchronously — never do blocking work before the response.

Do hooks fire during local development?

Yes, hooks fire in any environment where the gateway runs with a hooks block configured. Use separate development hook URLs to avoid hitting production monitoring with test events. The enabled: false field on individual hook entries is the cleanest way to pause specific hooks without removing their config.

TC
T. Chen
AI Systems Engineer

T. Chen builds and documents OpenClaw integration systems for production deployments. Has audited hook configurations across consumer apps, enterprise automation platforms, and multi-agent research pipelines — and written the internal integration guide used by dozens of OpenClaw teams.

Integration Reference

Complete OpenClaw hook schemas and integration patterns, free.