#Whatsapp webhook

1 messages Β· Page 1 of 1 (latest)

green summit
#

I want to create a webhook that accepts a message, and when i send this it runs a command to send a whatsapp message. How can I achieve this?

green summit
#

My webhooks are configured but it's not sending

#

when i post /hooks/agent i get back:
{
"ok": true,
"runId": "782bb3fa-13b4-4817-a6cf-9ee89277663a"
}

but it's not running the command/message

long onyxBOT
#

When you say β€œwebhooks”, do you mean OpenClaw Hooks (hooks.enabled β†’ POST /hooks/...) that should trigger a WhatsApp send, or a WhatsApp Cloud/Twilio webhook? (OpenClaw’s built-in WhatsApp channel is WhatsApp Web (Baileys) β€” there isn’t a separate Twilio/Cloud β€œwebhook channel”.)

Either way, the fastest way to pinpoint β€œconfigured but not sending” is:

  1. Version + baseline health
openclaw --version
openclaw status --all
openclaw channels status --probe

Paste the outputs (tokens are redacted in --all).

  1. Confirm the webhook request is actually accepted
    If you’re using OpenClaw Hooks, hit it with a known-good request and check the HTTP code:
#
curl -i -X POST http://127.0.0.1:18789/hooks/agent \
  -H 'x-openclaw-token: YOUR_HOOK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "message":"test webhook -> whatsapp",
    "deliver": true,
    "channel":"whatsapp",
    "to":"+15551234567"
  }'

Notes:

  • Query-string tokens are rejected (?token=... won’t work). Use Authorization: Bearer ... or x-openclaw-token.
  • If you’re behind a reverse proxy, it may drop the Authorization header β€” x-openclaw-token usually survives.
  1. Watch logs while you fire the webhook
#
openclaw logs --follow

Common log signatures:

  • 401/429 β†’ token/header/auth problem.
  • 202 accepted but no message β†’ usually one of:
    • no recipient info (fix by setting "channel" + "to" explicitly like above)
    • WhatsApp not actually connected (look for β€œno active listener when sending”; fix with openclaw channels login --channel whatsapp + ensure gateway is running)

Docs refs:

If you paste: (a) your openclaw --version, (b) the HTTP status you get from the curl, and (c) the relevant log lines right after the request, I can tell you exactly which branch you’re in.

#

That { ok: true, runId: ... } response is just the ack β€” /hooks/agent is async (it won’t return the agent’s reply in the HTTP response). The actual run either:

  1. is running but delivering β€œnowhere”, or
  2. is failing immediately (models/auth/channel), and the only place you’ll see why is the logs.

1) Most common gotcha: channel/to defaults don’t work for β€œwebhooks-only” setups

If you don’t set them, delivery defaults to:

  • channel: "last"
  • to: <last recipient in main session>

If your β€œmain session” has never chatted on any surface, there is no β€œlast recipient”, so the run can complete but nothing gets sent.

Try forcing delivery explicitly:

#
curl -i -X POST http://127.0.0.1:18789/hooks/agent \
  -H 'x-openclaw-token: YOUR_HOOK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "name":"hook test",
    "message":"Reply with exactly: webhook works",
    "wakeMode":"now",
    "deliver": true,
    "channel":"whatsapp",
    "to":"+15551234567"
  }'

(If you set wakeMode: "next-heartbeat" it may β€œdo nothing” until the next heartbeat tick.)

2) Tail logs by runId while firing the webhook

#

In one terminal:

openclaw logs --follow

Then fire the webhook and look for that runId. You’ll usually see one of these:

  • model/auth error (All models failed, No credentials found, 429s, etc.)
  • WhatsApp not connected / no listener
  • delivery target missing/invalid

3) Quick health checks to paste here

These three outputs usually make it obvious what’s wrong:

openclaw --version
#
openclaw models status
openclaw channels status --probe

If you can paste the exact JSON payload you’re POSTing to /hooks/agent (redact token) + the log lines around that runId, I can tell you precisely whether it’s a delivery-default issue, a model/auth failure, or WhatsApp connectivity.

green summit
#

Agents
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Agent β”‚ Bootstrap β”‚ Sessions β”‚ Active β”‚ Store β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ main β”‚ PENDING β”‚ 13 β”‚ 1m ago β”‚ ~/.openclaw/agents/main/sessions/sessions.json β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Diagnosis (read-only)

Gateway connection details:
Gateway target: ws://127.0.0.1:18789
Source: local loopback
Config: /root/.openclaw/openclaw.json
Bind: loopback

βœ“ Config: /root/.openclaw/openclaw.json
βœ“ Restart sentinel: none
! Port 18789
Port 18789 is already in use.

  • pid 72450 root: openclaw-gateway (127.0.0.1:18789)
  • Gateway already running locally. Stop it (openclaw gateway stop) or use a different port.
    βœ“ Tailscale: off Β· Running Β· sandbox-1.tail067cba.ts.net
    ips: 100.97.18.42, fd7a:115c:a1e0::8d3b:122a
    βœ“ Skills: 8 eligible Β· 0 missing Β· /root/.openclaw/workspace
    ! Channel issues skipped (gateway query failed)

Gateway health:
Error: gateway closed (1008): pairing required
Gateway target: ws://127.0.0.1:18789
Source: local loopback
Config: /root/.openclaw/openclaw.json
Bind: loopback

Pasteable debug report. Auth tokens redacted.
Troubleshooting: https://docs.openclaw.ai/troubleshooting

β—‡
root@sandbox-1:~#

#

Whats causing this?

logs on gateway:
f durationMs=6242 active=0 queued=0
02:48:03 warn gateway {"subsystem":"gateway"} security audit: device access upgrade requested reason=scope-upgrade device=55d0f223fecf3ebb0245f7b8ec151e288cba2d11e179c699a4761ce2c0b8540a ip=unknown-ip auth=token roleFrom=operator roleTo=operator scopesFrom=operator.admin,operator.approvals,operator.pairing scopesTo=operator.write client=gateway-client conn=73eeeeab-a7c2-4c3e-acad-08236276f8c2
02:48:03 error gateway connect failed: Error: pairing required
02:48:03 error gateway connect failed: Error: pairing required
02:48:03 error Subagent completion direct announce failed for run 5f70fa28-12b3-4ed6-b45b-55b4adb61f14:b51a79cc-1a6f-49e2-9e2f-1638db334791: gateway closed (1008): pairing required
Gateway target: ws://127.0.0.1:18789
Source: local loopback
Config: /root/.openclaw/openclaw.json
Bind: loopback
02:48:03 warn gateway/ws {"subsystem":"gateway/ws"} {"cause":"pairing-required","handshake":"failed","durationMs":13,"lastFrameType":"req","lastFrameMethod":"connect","lastFrameId":"c4b801fb-2d00-42e2-b91f-c3a6730922eb","host":"127.0.0.1:18789","deviceId":"55d0f223fecf3ebb0245f7b8ec151e288cba2d11e179c699a4761ce2c0b8540a","requestId":"641db701-ca37-4630-8d3c-4050e2f7f35e","reason":"scope-upgrade"} closed before connect conn=73eeeeab-a7c2-4c3e-acad-08236276f8c2 remote=127.0.0.1 fwd=n/a origin=n/a host=127.0.0.1:18789 ua=n/a code=1008 reason=pairing required

long onyxBOT
#

That status output is the smoking gun:

Gateway health: Error: gateway closed (1008): pairing required
Channel issues skipped (gateway query failed)

That means your operator device (CLI / Control UI) is not approved, so the gateway is rejecting the WebSocket control connection. Until that’s fixed, you won’t be able to reliably inspect what /hooks/agent is doing (and the dashboard/CLI can’t show you channel status, session output, etc.).

Fix the 1008 β€œpairing required” first (device approval)

Run on the gateway host (same machine where openclaw-gateway is running):

openclaw devices list
openclaw devices approve <requestId>

Then re-check:

#
openclaw status --deep
openclaw channels status --probe

Docs: https://docs.openclaw.ai/web/control-ui#device-pairing-first-connection

Second: your agent is still bootstrapping

Your table shows:

main Bootstrap: PENDING

So even if the hook run starts, the agent may be stuck doing the bootstrapping Q&A ritual instead of executing your β€œreal” message. Finish bootstrapping by chatting with it in Control UI (once pairing is approved) and answering the questions until BOOTSTRAP.md is removed.

Docs: https://docs.openclaw.ai/start/bootstrapping

Third: make sure your hook run has an explicit delivery target

#

If your payload doesn’t set channel + to, it defaults to channel:"last" and can end up delivering nowhere.

Use something like:

{
  "message": "say: webhook works",
  "deliver": true,
  "channel": "whatsapp",
  "to": "+15551234567",
  "wakeMode": "now"
}

If it still β€œacks but doesn’t run”

Tail logs and search for your runId:

#
openclaw logs --follow

Paste the log lines around 782bb3fa-13b4-4817-a6cf-9ee89277663a and the output of openclaw devices list (requestIds are fine to share). That will tell us if it’s failing on bootstrapping, model auth, WhatsApp connectivity, or delivery routing.

#

That log is not a WhatsApp/webhook problem yet β€” it’s the Gateway refusing a WebSocket β€œdevice” connection because it needs a scope upgrade and hasn’t been approved.

Key lines:

  • security audit: device access upgrade requested reason=scope-upgrade ... scopesTo=operator.write
  • handshake failed ... reason":"scope-upgrade" ... code=1008 reason=pairing required

Meaning: a client identified as deviceId 55d0f2... is trying to connect with a token that previously had limited operator scopes, and the gateway now wants to grant it operator.write (needed for β€œwrite” actions like sending/chat/etc). Until you approve that upgrade, the gateway closes the WS handshake with 1008 pairing required.

Fix

On the gateway host, as the same user/state dir the gateway is running under (yours shows /root/.openclaw/...):

openclaw devices list
openclaw devices approve 641db701-ca37-4630-8d3c-4050e2f7f35e
#

Then retry:

openclaw status --deep

If devices list shows nothing

You’re probably running the CLI against a different state directory than the gateway (common when the gateway is running as root in docker/systemd, but you run CLI as a normal user). Make sure the CLI is pointed at /root/.openclaw (or just run the commands as root in the same environment where the gateway runs).

Once that device approval is done, your /hooks/agent runs should start actually executing + delivering again, and channels status --probe will work too.

green summit
long onyxBOT
#

Yeah β€” openclaw agent ... does use LLM tokens, because it’s literally running an agent turn (same as if a chat message came in), so it will call whatever model you have configured and you’ll pay prompt+completion tokens.

Two related but different β€œtokens” people mean:

  1. LLM tokens (cost/usage)
  • Used by: openclaw agent
  • Not used by: openclaw message send (it just sends a message; no AI call)
  1. Gateway auth token (access control to the Gateway / Control UI / remote CLI)
  • openclaw agent goes through the Gateway by default (per the doc), so if your Gateway is protected (remote mode / non-local), the CLI may need the Gateway token/password to talk to it. That’s auth, not LLM usage.

If your webhook just needs β€œreceive HTTP β†’ send a WhatsApp message”, you probably want no AI at all, so use the messaging CLI:

openclaw message send --channel whatsapp --target +15555550123 --message "Hello from webhook"
green summit
#

How can I trigger openclaw message send from a webhook?

long onyxBOT
#

You’ve basically got two options:

Option A (no LLM tokens): webhook server β†’ run openclaw message send

This is the most direct way to β€œtrigger openclaw message send from a webhook”, and it doesn’t call a model.

Minimal Node/Express example (safe-ish spawning, no shell):

// server.js
import express from "express";
import { spawn } from "node:child_process";

const app = express();
app.use(express.json({ limit: "64kb" }));

const WEBHOOK_TOKEN = process.env.WEBHOOK_TOKEN; // your shared secret
#

app.post("/send-whatsapp", (req, res) => {
  // 1) auth
  if (!WEBHOOK_TOKEN || req.header("x-webhook-token") !== WEBHOOK_TOKEN) {
    return res.status(401).send("unauthorized");
  }

  // 2) input
  const { to, message } = req.body ?? {};
  if (typeof to !== "string" || typeof message !== "string") {
    return res.status(400).send("expected JSON: { to, message }");
  }

  // 3) run OpenClaw CLI
  const args = [
#
    "message",
    "send",
    "--channel",
    "whatsapp",
    "--target",
    to,
    "--message",
    message,
  ];

  const p = spawn("openclaw", args, { stdio: ["ignore", "pipe", "pipe"] });

  let out = "";
  let err = "";
  p.stdout.on("data", (d) => (out += d));
#
  p.stderr.on("data", (d) => (err += d));

  p.on("close", (code) => {
    if (code === 0) return res.status(200).send("ok");
    res.status(500).json({ code, err: err.trim(), out: out.trim() });
  });
});

app.listen(3001, () => console.log("listening on :3001"));

Run it:

npm i express
#
node server.js

Trigger it:

curl -X POST http://localhost:3001/send-whatsapp \
  -H "Content-Type: application/json" \
  -H "x-webhook-token: YOUR_SECRET" \
  -d '{"to":"+15555550123","message":"hello from webhook"}'

Notes that matter in practice

  • Run this on the same machine as your OpenClaw Gateway (simplest), so it uses the same ~/.openclaw/openclaw.json config + WhatsApp channel setup.
  • Don’t use exec("openclaw ... " + userInput) (command injection risk). spawn([...args]) like above is the right pattern.
  • Add rate limiting + strict validation if this is internet-exposed.