From 7a44225481f8a8fd63bab410df066f5a916c9462 Mon Sep 17 00:00:00 2001 From: zap Date: Thu, 12 Mar 2026 06:47:00 +0000 Subject: [PATCH] feat(skill): add local n8n webhook skill draft --- memory/2026-03-12.md | 1 + skills/n8n-webhook/SKILL.md | 78 ++++++++++ skills/n8n-webhook/references/payloads.md | 76 ++++++++++ skills/n8n-webhook/scripts/call-webhook.sh | 159 +++++++++++++++++++++ 4 files changed, 314 insertions(+) create mode 100644 skills/n8n-webhook/SKILL.md create mode 100644 skills/n8n-webhook/references/payloads.md create mode 100755 skills/n8n-webhook/scripts/call-webhook.sh diff --git a/memory/2026-03-12.md b/memory/2026-03-12.md index f7a7849..675bbe6 100644 --- a/memory/2026-03-12.md +++ b/memory/2026-03-12.md @@ -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`. diff --git a/skills/n8n-webhook/SKILL.md b/skills/n8n-webhook/SKILL.md new file mode 100644 index 0000000..3012f07 --- /dev/null +++ b/skills/n8n-webhook/SKILL.md @@ -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 diff --git a/skills/n8n-webhook/references/payloads.md b/skills/n8n-webhook/references/payloads.md new file mode 100644 index 0000000..8b3a4a5 --- /dev/null +++ b/skills/n8n-webhook/references/payloads.md @@ -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. diff --git a/skills/n8n-webhook/scripts/call-webhook.sh b/skills/n8n-webhook/scripts/call-webhook.sh new file mode 100755 index 0000000..0dcc56b --- /dev/null +++ b/skills/n8n-webhook/scripts/call-webhook.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + scripts/call-webhook.sh [--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