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
|
||||
- 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`.
|
||||
- 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