# OpenClaw Webhooks

## When to Use This Skill

* Receiving webhook calls from an OpenClaw Gateway
* Verifying `Authorization: Bearer <token>` or `x-openclaw-token` headers
* Handling `/hooks/agent` and `/hooks/wake` event payloads
* Building external services that react to OpenClaw agent activity

## Essential Code (USE THIS)

### OpenClaw Token Verification (JavaScript)

```javascript
const crypto = require('crypto');

function verifyOpenClawWebhook(authHeader, xTokenHeader, secret) {
  // OpenClaw sends the token in one of two headers:
  //   Authorization: Bearer <token>
  //   x-openclaw-token: <token>
  const token = extractToken(authHeader, xTokenHeader);
  if (!token || !secret) return false;

  try {
    return crypto.timingSafeEqual(
      Buffer.from(token),
      Buffer.from(secret)
    );
  } catch {
    return false;
  }
}

function extractToken(authHeader, xTokenHeader) {
  if (xTokenHeader) return xTokenHeader;
  if (authHeader && authHeader.startsWith('Bearer '))
    return authHeader.slice(7);
  return null;
}

```

### Express Webhook Handler

```javascript
const express = require('express');
const app = express();

app.post('/webhooks/openclaw',
  express.json(),
  (req, res) => {
    const authHeader = req.headers['authorization'];
    const xToken = req.headers['x-openclaw-token'];

    if (!verifyOpenClawWebhook(authHeader, xToken, process.env.OPENCLAW_HOOK_TOKEN)) {
      console.error('OpenClaw token verification failed');
      return res.status(401).send('Invalid token');
    }

    const { message, name, wakeMode, agentId, sessionKey } = req.body;

    console.log(`[${name || 'OpenClaw'}] ${message}`);

    // Respond quickly - OpenClaw expects 200 or 202
    res.status(200).json({ received: true });
  }
);

```

### Python Token Verification (FastAPI)

```python
import hmac

def verify_openclaw_webhook(auth_header: str | None, x_token: str | None, secret: str) -> bool:
    token = x_token
    if not token and auth_header and auth_header.startswith("Bearer "):
        token = auth_header[7:]
    if not token or not secret:
        return False
    return hmac.compare_digest(token, secret)

```

> For complete working examples with tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/openclaw-webhooks/examples/express/) - Full Express implementation
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/openclaw-webhooks/examples/nextjs/) - Next.js App Router implementation
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/openclaw-webhooks/examples/fastapi/) - Python FastAPI implementation

## Webhook Endpoints

OpenClaw Gateway exposes two webhook endpoints. Your external service receives POSTs from the Gateway (or a relay like Hookdeck) on a URL you choose.

| Endpoint | Purpose | Response |
| --- | --- | --- |
| `POST /hooks/agent` | Trigger an isolated agent turn | `202 Accepted` |
| `POST /hooks/wake` | Enqueue a system event | `200 OK` |

## Agent Hook Payload

```json
{
  "message": "Summarize inbox",
  "name": "Email",
  "agentId": "hooks",
  "sessionKey": "hook:email:msg-123",
  "wakeMode": "now",
  "deliver": true,
  "channel": "last",
  "to": "+15551234567",
  "model": "openai/gpt-5.2-mini",
  "thinking": "low",
  "timeoutSeconds": 120
}

```

| Field | Required | Description |
| --- | --- | --- |
| `message` | Yes | Prompt or message for the agent |
| `name` | No | Human-readable hook name (e.g. "GitHub", "Email") |
| `agentId` | No | Route to a specific agent; falls back to default |
| `sessionKey` | No | Session key (disabled by default) |
| `wakeMode` | No | `now` (default) or `next-heartbeat` |
| `deliver` | No | Send agent response to messaging channel (default `true`) |
| `channel` | No | `last`, `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `msteams` |
| `to` | No | Recipient identifier for the channel |
| `model` | No | Model override for this run |
| `thinking` | No | Thinking level: `low`, `medium`, `high` |
| `timeoutSeconds` | No | Max duration for the agent run |

## Wake Hook Payload

```json
{
  "text": "New email received",
  "mode": "now"
}

```

| Field | Required | Description |
| --- | --- | --- |
| `text` | Yes | Description of the event |
| `mode` | No | `now` (default) or `next-heartbeat` |

## Authentication Headers

OpenClaw supports two header styles. Pick one:

| Header | Format |
| --- | --- |
| `Authorization` | `Bearer <token>` (recommended) |
| `x-openclaw-token` | `<token>` |

Query-string tokens (`?token=...`) are rejected with `400`.

## Response Codes

| Code | Meaning |
| --- | --- |
| `200` | Wake event accepted |
| `202` | Agent hook accepted (async run started) |
| `400` | Invalid payload or query-string token |
| `401` | Authentication failed |
| `413` | Payload too large |
| `429` | Rate-limited (check `Retry-After` header) |

## Environment Variables

```bash
OPENCLAW_HOOK_TOKEN=your_shared_secret   # Must match hooks.token in Gateway config

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 openclaw --path /webhooks/openclaw

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/openclaw-webhooks/references/overview.md) - OpenClaw webhook concepts and architecture
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/openclaw-webhooks/references/setup.md) - Gateway configuration guide
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/openclaw-webhooks/references/verification.md) - Token verification details