feat(skill): add local n8n webhook skill draft
This commit is contained in:
@@ -9,3 +9,4 @@
|
|||||||
- `openclaw-ping` webhook path tested end-to-end
|
- `openclaw-ping` webhook path tested end-to-end
|
||||||
- Operating note: prefer narrow webhook-first integration rather than broad n8n admin/API access.
|
- Operating note: prefer narrow webhook-first integration rather than broad n8n admin/API access.
|
||||||
- Will clarified the primary host LAN IP to use/document is `192.168.153.113`.
|
- Will clarified the primary host LAN IP to use/document is `192.168.153.113`.
|
||||||
|
- Drafted local skill `skills/n8n-webhook` for authenticated webhook-first n8n integration, including `scripts/call-webhook.sh`, payload notes, and a successful package/validation run to `/tmp/n8n-skill-dist/n8n-webhook.skill`.
|
||||||
|
|||||||
78
skills/n8n-webhook/SKILL.md
Normal file
78
skills/n8n-webhook/SKILL.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
name: n8n-webhook
|
||||||
|
description: Trigger authenticated local n8n webhooks on the LAN for OpenClaw-to-n8n integration. Use when calling safe, narrow workflows on the dedicated local n8n-agent instance, such as ping/test endpoints, action-bus style workflows, notifications, logging, or other preapproved webhook entrypoints. Do not use for broad n8n admin/API management, workflow mutation, credential management, or unrestricted orchestration.
|
||||||
|
---
|
||||||
|
|
||||||
|
# N8n Webhook
|
||||||
|
|
||||||
|
Use this skill to call the local `n8n-agent` webhook surface on:
|
||||||
|
|
||||||
|
- `http://192.168.153.113:18808` (primary LAN)
|
||||||
|
- `http://100.123.88.127:18808` (Tailscale)
|
||||||
|
|
||||||
|
Keep the integration narrow: let OpenClaw decide what to do, and let n8n execute a small set of explicit workflows.
|
||||||
|
|
||||||
|
## Policy
|
||||||
|
|
||||||
|
1. Prefer named webhook entrypoints over generic admin APIs.
|
||||||
|
2. Send JSON and expect JSON back.
|
||||||
|
3. Use header auth by default (`x-openclaw-secret`).
|
||||||
|
4. Use `/webhook-test/` only while building/editing a workflow.
|
||||||
|
5. Surface non-2xx responses clearly instead of pretending success.
|
||||||
|
6. If a new workflow is needed, define its request/response contract before wiring callers.
|
||||||
|
|
||||||
|
## Quick usage
|
||||||
|
|
||||||
|
Set the shared secret once for the shell session:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export N8N_WEBHOOK_SECRET='replace-me'
|
||||||
|
```
|
||||||
|
|
||||||
|
Call a production webhook:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/call-webhook.sh openclaw-ping --data '{"message":"hello from OpenClaw"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Call a test webhook while editing a flow:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/call-webhook.sh openclaw-ping --test --data '{"message":"hello from OpenClaw"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Pretty-print JSON response:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/call-webhook.sh openclaw-ping --pretty --data '{"message":"hello"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Call an existing safe webhook
|
||||||
|
|
||||||
|
1. Confirm the target webhook path is already intended for agent use.
|
||||||
|
2. Use `scripts/call-webhook.sh` with JSON input.
|
||||||
|
3. Treat any non-2xx response as a failure that needs investigation.
|
||||||
|
|
||||||
|
Current known endpoint:
|
||||||
|
|
||||||
|
- `openclaw-ping` — basic end-to-end connectivity check
|
||||||
|
|
||||||
|
### Add a new webhook-backed capability
|
||||||
|
|
||||||
|
1. Write down the webhook path, required auth, request JSON, and response JSON.
|
||||||
|
2. If the shape is more than trivial, read `references/payloads.md` first.
|
||||||
|
3. Keep the first version small and explicit.
|
||||||
|
4. Only add the new endpoint to regular use after a successful `/webhook-test/` run.
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
- `N8N_BASE_URL` — override base URL (default `http://192.168.153.113:18808`)
|
||||||
|
- `N8N_WEBHOOK_SECRET` — required shared secret for authenticated calls
|
||||||
|
- `N8N_SECRET_HEADER` — header name (default `x-openclaw-secret`)
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- `scripts/call-webhook.sh` — authenticated POST helper for local n8n webhooks
|
||||||
|
- `references/payloads.md` — suggested request/response contracts and naming conventions
|
||||||
76
skills/n8n-webhook/references/payloads.md
Normal file
76
skills/n8n-webhook/references/payloads.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# n8n-webhook payload notes
|
||||||
|
|
||||||
|
## Current live endpoint
|
||||||
|
|
||||||
|
### `openclaw-ping`
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
- confirm OpenClaw can reach the local n8n webhook surface end-to-end
|
||||||
|
|
||||||
|
Typical request body:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "hello from OpenClaw"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended success response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"service": "n8n-agent",
|
||||||
|
"message": "openclaw webhook reached"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recommended contract for new endpoints
|
||||||
|
|
||||||
|
Prefer a stable JSON shape like:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"request_id": "optional-uuid",
|
||||||
|
"result": {
|
||||||
|
"...": "workflow-specific payload"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
On failure, return a non-2xx status plus:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": false,
|
||||||
|
"error": {
|
||||||
|
"code": "short-machine-code",
|
||||||
|
"message": "human-readable summary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Naming guidance
|
||||||
|
|
||||||
|
- Use lowercase kebab-case webhook paths.
|
||||||
|
- Keep names explicit: `openclaw-ping`, `openclaw-action`, `append-log`, `send-notify`.
|
||||||
|
- Avoid generic names like `run`, `task`, or `webhook1`.
|
||||||
|
|
||||||
|
## Suggested action-bus pattern
|
||||||
|
|
||||||
|
If multiple agent-safe actions are needed, prefer one router webhook such as `openclaw-action`.
|
||||||
|
|
||||||
|
Request shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "append_log",
|
||||||
|
"args": {
|
||||||
|
"text": "backup finished"
|
||||||
|
},
|
||||||
|
"request_id": "optional-uuid"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That keeps the external surface small while letting n8n route internally.
|
||||||
159
skills/n8n-webhook/scripts/call-webhook.sh
Executable file
159
skills/n8n-webhook/scripts/call-webhook.sh
Executable file
@@ -0,0 +1,159 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
scripts/call-webhook.sh <path-or-url> [--data '{"k":"v"}'] [--data-file payload.json] [--test] [--pretty] [--dry-run]
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
N8N_BASE_URL Base n8n URL (default: http://192.168.153.113:18808)
|
||||||
|
N8N_WEBHOOK_SECRET Shared secret value for header auth (required unless --dry-run)
|
||||||
|
N8N_SECRET_HEADER Header name for shared secret (default: x-openclaw-secret)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
scripts/call-webhook.sh openclaw-ping --data '{"message":"hello"}'
|
||||||
|
scripts/call-webhook.sh openclaw-ping --test --data-file payload.json
|
||||||
|
echo '{"message":"hello"}' | scripts/call-webhook.sh openclaw-ping --pretty
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
BASE_URL="${N8N_BASE_URL:-http://192.168.153.113:18808}"
|
||||||
|
SECRET_HEADER="${N8N_SECRET_HEADER:-x-openclaw-secret}"
|
||||||
|
SECRET_VALUE="${N8N_WEBHOOK_SECRET:-}"
|
||||||
|
MODE="prod"
|
||||||
|
PRETTY=0
|
||||||
|
DRY_RUN=0
|
||||||
|
PATH_OR_URL=""
|
||||||
|
DATA=""
|
||||||
|
DATA_FILE=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--help|-h)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--test)
|
||||||
|
MODE="test"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--prod)
|
||||||
|
MODE="prod"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--pretty)
|
||||||
|
PRETTY=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
DRY_RUN=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--data)
|
||||||
|
DATA="${2:?missing value for --data}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--data-file)
|
||||||
|
DATA_FILE="${2:?missing value for --data-file}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -z "$PATH_OR_URL" ]]; then
|
||||||
|
PATH_OR_URL="$1"
|
||||||
|
else
|
||||||
|
echo "Unexpected extra argument: $1" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$PATH_OR_URL" ]]; then
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$DATA" && -n "$DATA_FILE" ]]; then
|
||||||
|
echo "Use either --data or --data-file, not both." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$DATA_FILE" ]]; then
|
||||||
|
DATA="$(cat "$DATA_FILE")"
|
||||||
|
elif [[ -n "$DATA" ]]; then
|
||||||
|
:
|
||||||
|
elif ! [[ -t 0 ]]; then
|
||||||
|
DATA="$(cat)"
|
||||||
|
else
|
||||||
|
DATA='{}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 - <<'PY' "$DATA"
|
||||||
|
import json, sys
|
||||||
|
json.loads(sys.argv[1])
|
||||||
|
PY
|
||||||
|
|
||||||
|
if [[ "$PATH_OR_URL" =~ ^https?:// ]]; then
|
||||||
|
URL="$PATH_OR_URL"
|
||||||
|
else
|
||||||
|
PREFIX="webhook"
|
||||||
|
[[ "$MODE" == "test" ]] && PREFIX="webhook-test"
|
||||||
|
URL="${BASE_URL%/}/${PREFIX}/${PATH_OR_URL#/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
||||||
|
printf 'POST %s\n' "$URL"
|
||||||
|
printf 'Header: Content-Type: application/json\n'
|
||||||
|
if [[ -n "$SECRET_VALUE" ]]; then
|
||||||
|
printf 'Header: %s: [set]\n' "$SECRET_HEADER"
|
||||||
|
else
|
||||||
|
printf 'Header: %s: [missing]\n' "$SECRET_HEADER"
|
||||||
|
fi
|
||||||
|
printf 'Body: %s\n' "$DATA"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$SECRET_VALUE" ]]; then
|
||||||
|
echo "N8N_WEBHOOK_SECRET is required for non-dry-run calls." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
BODY_FILE="$(mktemp)"
|
||||||
|
trap 'rm -f "$BODY_FILE"' EXIT
|
||||||
|
|
||||||
|
HTTP_CODE="$({
|
||||||
|
curl -sS \
|
||||||
|
-o "$BODY_FILE" \
|
||||||
|
-w '%{http_code}' \
|
||||||
|
-X POST "$URL" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H "$SECRET_HEADER: $SECRET_VALUE" \
|
||||||
|
--data "$DATA"
|
||||||
|
} || true)"
|
||||||
|
|
||||||
|
if [[ -z "$HTTP_CODE" ]]; then
|
||||||
|
echo "Request failed before receiving an HTTP status." >&2
|
||||||
|
cat "$BODY_FILE" >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$HTTP_CODE" =~ ^2[0-9][0-9]$ ]]; then
|
||||||
|
if [[ "$PRETTY" -eq 1 ]]; then
|
||||||
|
python3 -m json.tool "$BODY_FILE" 2>/dev/null || cat "$BODY_FILE"
|
||||||
|
else
|
||||||
|
cat "$BODY_FILE"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "n8n webhook call failed with HTTP $HTTP_CODE" >&2
|
||||||
|
cat "$BODY_FILE" >&2 || true
|
||||||
|
exit 1
|
||||||
Reference in New Issue
Block a user