Gareth Wilson Gareth Wilson

Guide to OpenClaw Webhooks: Features and Best Practices

Published


OpenClaw is a self-hosted gateway that runs AI agents and connects them to chat apps, tools and schedules. Its automations, also called cron jobs, run an agent prompt, a command or a script on a schedule. Usually the result is announced in a chat, but an automation can instead POST its result to a URL. That makes an OpenClaw agent a source of events for your own systems: a daily digest written to a database, a scheduled check that opens a ticket, or a failed run that pages someone.

This guide covers the webhooks OpenClaw sends when automations finish or fail: how to set them up, how to authenticate them, what they contain, and how OpenClaw records delivery. OpenClaw also receives webhooks on its own /hooks endpoints. That's the other direction, and our tutorial on using Hookdeck with OpenClaw covers it.

What are OpenClaw webhooks?

Every OpenClaw automation has a delivery mode that decides what happens with its output:

ModeWhat happens
announceSends the result to a chat and records confirmed delivery
webhookPOSTs the finished event payload to a URL
noneNo automatic result or notification

In webhook mode, OpenClaw sends a POST with a JSON body to your URL when a run finishes. Separately, failure alerts for an automation can also be sent to a webhook.

OpenClaw doesn't sign these requests. If you set cron.webhookToken in the Gateway's configuration, every automation webhook POST carries Authorization: Bearer <token>. That header is the only way to check that a request came from your OpenClaw Gateway.

OpenClaw webhook features

FeatureDetails
Configuration--webhook <url> on openclaw automations add, create or edit
Method and bodyPOST with a JSON finished-run payload
AuthenticationAuthorization: Bearer <cron.webhookToken>
SignatureNone
Token scopeOne token in the Gateway config, used for every automation webhook
Credentials in URLNot allowed
Outbound policyStrict SSRF guard. Private and loopback targets need an exemption
Empty resultsA successful run with an empty summary sends nothing
ErrorsAlways sent, even without a summary
SuccessAny 2xx records the delivery
Lost responsesRecorded as Unknown and not retried
Failure alertsOptional webhook destination with its own threshold and cooldown
Payload schemaNot in the docs. Fields below come from OpenClaw's source code

What OpenClaw sends

OpenClaw's documentation says only that webhook mode will "POST finished event payload to a URL". It doesn't list the payload's fields. The fields below come from OpenClaw's source code, so they may change between releases. Read them defensively.

A finished-run payload looks like this:

{
  "jobId": "job_123",
  "action": "finished",
  "runId": "run_abc",
  "runAtMs": 1767268800000,
  "durationMs": 4200,
  "status": "ok",
  "completionStatus": "succeeded",
  "summary": "Three deploys today, all green.",
  "job": { "id": "job_123", "name": "Deploy digest" },
  "nextRunAtMs": 1767355200000
}
FieldDescription
actionfinished for this delivery
jobId, jobThe automation's ID and a snapshot of its definition
runId, runAtMs, durationMsThe run's ID, start time in milliseconds, and duration
statusExecution status, such as ok or error
completionStatusWhether the run's required delivery completed, such as succeeded or failed
summaryThe run's output. Left out on error events
errorError text for failed runs
nextRunAtMsThe next scheduled run, when there is one

status and completionStatus are separate. The docs say a run can record status: "ok" with completionStatus: "failed" when execution succeeded but required delivery didn't.

Failure alerts sent to a webhook have a smaller body in the source: jobId, jobName, message and runAtMs. OpenClaw's docs confirm the structured runAtMs field, and say failure webhooks keep the raw error for diagnostic integrations.

Setting up OpenClaw webhooks

  1. Choose a long random token and set it in the Gateway configuration:
{
  cron: {
    webhookToken: "replace-with-dedicated-webhook-token",
  },
}
  1. Create an automation that delivers to your URL:
openclaw automations create "0 18 * * 1-5" \
  "Summarize today's deploys as JSON." \
  --name "Deploy digest" \
  --webhook "https://example.com/webhooks/openclaw"

Use openclaw automations edit <job-id> --webhook <url> to switch an existing job. Webhook delivery can't be combined with the chat delivery flags --announce, --channel, --to, --thread-id or --account.

  1. Optionally, send failure alerts to a webhook too, through the job's delivery.failureDestination in webhook mode. By default, a job with a failure route alerts after 2 consecutive failures, with a 1-hour cooldown, and repeated failures with the same cause count as one incident. openclaw automations edit has flags to tune the threshold, cooldown and mode per job.

Don't put credentials in the URL. OpenClaw's docs say webhook URLs must not include an embedded username and password, and to use webhookToken instead.

The SSRF guard

Every outbound automation webhook goes through a strict SSRF guard. Loopback, private, internal, link-local and other special-use addresses are refused by default, which covers primary delivery, completion and failure destinations, and failure-alert webhooks. A public HTTPS URL, such as a Hookdeck source URL, works without changes. To reach a receiver on your own network, add its exact hostname or IP:

{
  cron: {
    webhookSsrfPolicy: {
      allowedHostnames: ["127.0.0.1"],
    },
  },
}

dangerouslyAllowPrivateNetwork: true opens every private address to every automation webhook. OpenClaw recommends narrow allowedHostnames entries instead.

Securing OpenClaw webhooks

Compare the whole Authorization header with Bearer followed by your token, in constant time. There's no body signature to check, so this is the entire verification.

const crypto = require('crypto');
const express = require('express');

function verifyOpenClawWebhook(authorizationHeader, webhookToken) {
  if (!authorizationHeader || !webhookToken) return false;
  const received = Buffer.from(authorizationHeader, 'utf8');
  const expected = Buffer.from(`Bearer ${webhookToken}`, 'utf8');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

const app = express();

app.post('/webhooks/openclaw', express.raw({ type: '*/*' }), (req, res) => {
  const webhook_token = process.env.OPENCLAW_WEBHOOK_TOKEN;
  if (!webhook_token) return res.sendStatus(500); // fail closed

  if (!verifyOpenClawWebhook(req.get('authorization'), webhook_token)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  if (event.action === 'finished') {
    // event.jobId, event.runId, event.status, event.summary or event.error
  } else if (event.jobName && event.message) {
    // A failure alert
  }
  res.sendStatus(200); // any 2xx counts as delivered
});

The same check in Python:

import hmac


def verify_openclaw_webhook(authorization_header, webhook_token: str) -> bool:
    if not authorization_header or not webhook_token:
        return False
    expected = f"Bearer {webhook_token}".encode("utf-8")
    return hmac.compare_digest(authorization_header.encode("utf-8"), expected)


# In FastAPI:
# verify_openclaw_webhook(request.headers.get("authorization"),
#                         os.environ["OPENCLAW_WEBHOOK_TOKEN"])

OpenClaw only sends the header when cron.webhookToken is set, so always set it. Without it, anyone who finds your URL can post fake automation results.

In Hookdeck, the OpenClaw source type checks the Authorization header. Enter the whole value, Bearer followed by your webhookToken.

Make OpenClaw webhooks production-ready. Hookdeck Event Gateway checks the bearer token, stores every automation result as soon as OpenClaw sends it, and retries delivery to your service, which OpenClaw itself won't do.

OpenClaw webhook limitations and pain points

No retries for uncertain deliveries

The Problem: An automation result is lost because your receiver was slow or restarting.

Why It Happens: OpenClaw records a delivery after a 2xx, and records Not delivered after an HTTP rejection. If the request may have reached the receiver but the response is lost or times out, delivery stays Unknown, and the transport doesn't retry that send. The docs describe the same behaviour for failure-alert webhooks.

Workarounds:

  • Respond with 2xx as soon as the request is authenticated and stored.
  • Check automation run history for Not delivered and Unknown results.
  • Run the job again manually if a result matters and didn't arrive.

How Hookdeck Can Help: Hookdeck acknowledges each request at its edge, so OpenClaw records a delivery, and then holds the event until your service accepts it. Failed deliveries to your service are retried and can be replayed.

One shared token, no signature

The Problem: Every automation on a Gateway uses the same secret, and a leaked token lets anyone send results for any job.

Why It Happens: cron.webhookToken is a single Gateway setting sent as a bearer header. Nothing ties a request to a particular job or protects the body.

Workarounds:

  • Use a dedicated token for automation webhooks, separate from the Gateway's other tokens and from hooks.token.
  • Give each automation its own URL path, and check that jobId matches the path you expect.
  • Use HTTPS and keep the token out of logs.

How Hookdeck Can Help: Hookdeck checks the token at the source and records any request that fails, so you can see when something other than your Gateway is calling the URL.

Silent successes

The Problem: A scheduled job ran, but your system never heard about it.

Why It Happens: A successful run with an empty summary deliberately skips the POST and records deliverySuppressionReason: "empty". Errors are still sent.

Workarounds:

  • Make the automation's prompt or script always produce output, even "nothing to report".
  • Use nextRunAtMs from earlier deliveries to notice a run that should have reported and didn't.

How Hookdeck Can Help: Hookdeck's event log shows exactly when each result arrived, which makes a missing run easy to spot.

Undocumented payload

The Problem: A field your handler depends on is renamed or removed after an OpenClaw upgrade.

Why It Happens: The docs don't define the webhook payload. The field names come from the source code and aren't a published contract.

Workarounds:

  • Read fields defensively, with defaults, and log payloads that don't match what you expect.
  • Pin your OpenClaw version and test webhooks after each upgrade.

How Hookdeck Can Help: Every request is stored in Hookdeck, so after an upgrade you can compare old and new payloads and replay them once your handler is fixed. Transformations can map a changed payload back to the shape your service expects.

Best practices

Always set webhookToken

Without it, OpenClaw sends no Authorization header and your endpoint can't tell a real result from a fake one.

Compare the whole header in constant time

Compare Authorization with Bearer <token> using crypto.timingSafeEqual or hmac.compare_digest, check lengths first in Node, and fail closed if the token isn't configured.

Deduplicate on runId

Handle each run once. Use runId, or jobId with runAtMs for failure alerts, which have no runId. See our guide to webhook idempotency.

Handle results and failures separately

Check action and status before using summary, which is left out on errors, and treat failure alerts, which have jobName and message, as their own event type.

Acknowledge fast and process asynchronously

Store the result, return 200, and process afterwards. See why to process webhooks asynchronously.

Conclusion

OpenClaw automations can send their results to any public URL with a single --webhook flag. Authentication is a bearer token you configure once per Gateway, the payload is defined only by the source code, and a delivery whose response is lost isn't retried. Setting the token, verifying it carefully and putting a durable receiver in front of your service covers most of the risk.

Hookdeck Event Gateway sits in front of your receiver, checks the token, stores every result, and delivers it to your service with retries you control.

Get started with Hookdeck for free and handle OpenClaw webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.