When the user wants to connect an OpenClaw agent to n8n workflows, create n8n webhook skills for OpenClaw, route agent API calls through n8n for credential i...
Build secure, auditable bridges between OpenClaw autonomous agents and n8n deterministic workflows. This skill generates production-ready OpenClaw skill directories (SKILL.md + scripts) that route all external API interactions through n8n's locked, credentialed pipelines.
OpenClaw's exec tool grants the LLM shell-level access. Every API key stored in .env or skill files becomes an attack vector — leakable through hallucination, prompt injection, or plaintext logging. The proxy orchestration pattern eliminates this:
User says: "trigger n8n workflow," "create webhook skill," "call n8n from openclaw" → Generate an OpenClaw skill directory that triggers n8n webhooks
User says: "push data to openclaw," "n8n response to agent," "gateway API"
→ Configure n8n HTTP Request nodes to POST to the Gateway /v1/responses endpoint
User says: "round trip," "full integration," "proxy pattern," "credential isolation" → Complete egress + ingress loop with air-gap credential provisioning
User says: "rebuild agent in n8n," "n8n-claw," "supabase memory" → Read references/n8n-claw-architecture.md
User says: "publish skill," "clawhub," "package for registry" → Read references/publishing.md
When building an OpenClaw skill that triggers n8n, generate a complete skill directory — not a loose file. OpenClaw skills are directories containing a SKILL.md with YAML frontmatter.
openclaw-{service}-{action}/
├── SKILL.md ← YAML frontmatter + instructions (REQUIRED)
├── README.md ← Human documentation
└── scripts/
└── trigger.sh ← Webhook trigger with input sanitization
Every generated SKILL.md must follow this exact structure:
---
name: openclaw-{service}-{action}
description: "When the user requests {specific trigger condition}, POST a structured JSON payload to the n8n webhook at {path} to execute {what it does}. Use this skill for {explicit use cases}."
homepage: "https://github.com/{user}/{repo}"
metadata:
clawdbot:
emoji: "{relevant_emoji}"
requires:
env:
- N8N_WEBHOOK_URL
- N8N_WEBHOOK_SECRET
bins:
- "curl"
files:
- "scripts/*"
user-invocable: true
---
Critical YAML rules:
description is the only trigger mechanism. The LLM cannot see the Markdown body until after it selects the skill. Pack ALL trigger heuristics into this single string.metadata.clawdbot namespace — NOT metadata.openclaw (the registry ignores the legacy namespace).requires.env (singular) — NOT requires.envs (causes parse validation errors).files: ["scripts/*"] — omitting this causes ClawHub security scanners to flag the skill as suspicious.Below the frontmatter, write instructions the LLM will follow. Include these mandatory transparency sections for ClawHub compliance:
# {Skill Name}
## Execution
When triggered, run the webhook script:
\`\`\`bash
exec scripts/trigger.sh "{action}" "{payload_json}"
\`\`\`
The script sanitizes all input, constructs the authenticated webhook request,
and returns the n8n response for the agent to summarize.
## Failure Handling
- If curl returns non-zero exit code, read stderr and report the specific error
- If HTTP 401: webhook secret mismatch — instruct user to verify N8N_WEBHOOK_SECRET
- If HTTP 404: workflow inactive or path wrong — instruct user to check n8n
- If HTTP 5xx: n8n execution failure — report and suggest checking n8n execution logs
- Do NOT hallucinate success if the response is empty or unexpected
## External Endpoints
| URL | Method | Payload |
|-----|--------|---------|
| `${N8N_WEBHOOK_URL}/webhook/openclaw-{service}-{action}` | POST | JSON action + payload object |
## Security & Privacy
- Webhook secret loaded from environment variable, never hardcoded
- All user input sanitized via URL encoding before shell execution
- No data stored locally; payload transmitted to n8n instance only
- Agent has NO access to downstream API credentials (Slack, GitHub, etc.)
## Model Invocation Note
The OpenClaw agent may invoke this skill autonomously without direct human
prompting. To disable autonomous triggering, remove this skill directory
from the active skills path.
## Trust Statement
By using this skill, data is sent to your configured n8n instance.
Only install if you trust your n8n deployment and its configured integrations.
Every webhook trigger script MUST include:
set -euo pipefail (non-negotiable — prevents silent failures)urllib.parse.quote--data-urlencode or pre-encoded payloads (never raw string interpolation)#!/usr/bin/env bash
set -euo pipefail
# SECURITY MANIFEST:
# Environment variables accessed: N8N_WEBHOOK_URL, N8N_WEBHOOK_SECRET (only)
# External endpoints called: ${N8N_WEBHOOK_URL}/webhook/openclaw-{service}-{action} (only)
# Local files read: none
# Local files written: none
ACTION="${1:?Usage: trigger.sh <action> <payload_json>}"
PAYLOAD="${2:?Usage: trigger.sh <action> <payload_json>}"
# Sanitize inputs — prevents shell injection via command substitution
SAFE_ACTION=$(printf '%s' "$ACTION" | python3 -c \
'import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip(), safe=""))')
# Construct and send webhook request
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
"${N8N_WEBHOOK_URL}/webhook/openclaw-${SAFE_ACTION}" \
-H "Content-Type: application/json" \
-H "x-webhook-secret: ${N8N_WEBHOOK_SECRET}" \
--data-urlencode "payload=${PAYLOAD}" \
--max-time 30)
# Parse response
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [[ "$HTTP_CODE" -ge 200 && "$HTTP_CODE" -lt 300 ]]; then
echo "SUCCESS (HTTP ${HTTP_CODE}): ${BODY}"
exit 0
else
echo "ERROR (HTTP ${HTTP_CODE}): ${BODY}" >&2
exit 1
fi
For simple webhook calls, prefer OpenClaw's built-in exec tool with a Node.js one-liner over shell scripts. Node.js avoids shell expansion vulnerabilities entirely:
// Inline via exec tool — no external dependencies
const https = require('https');
const data = JSON.stringify({action: ACTION, payload: PAYLOAD});
const options = {
hostname: N8N_HOST, port: 5678, path: '/webhook/openclaw-{service}-{action}',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-webhook-secret': process.env.N8N_WEBHOOK_SECRET,
'Content-Length': data.length
}
};
const req = https.request(options, res => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
process.stdout.write(body);
process.exit(res.statusCode >= 200 && res.statusCode < 300 ? 0 : 1);
});
});
req.on('error', e => { process.stderr.write(e.message); process.exit(1); });
req.write(data);
req.end();
Node.js is preferred because OpenClaw requires Node.js 22+ — it's always available. No dependencies, no shell injection surface.
The Gateway /v1/responses endpoint is disabled by default.
Edit openclaw.json:
{
"gateway": {
"http": {
"endpoints": {
"responses": { "enabled": true }
}
},
"auth": {
"mode": "token",
"token": "${OPENCLAW_GATEWAY_TOKEN}"
}
}
}
| Parameter | Value | Why |
|---|---|---|
| Method | POST | Standard transmission |
| URL | http://{gateway-host}:18789/v1/responses | OpenResponses ingress |
| Authorization | Bearer {token} | Gateway auth |
| Content-Type | application/json | JSON parsing |
| timeout_seconds | 0 | MANDATORY — prevents infinite echo loop |
The echo loop problem: Without timeout_seconds: 0, the Gateway event bus reverberates the payload back into the session, causing the LLM to hallucinate an infinite dialogue with its own data. This destroys the 64k token context window and burns API costs exponentially.
{
"model": "claude-sonnet-4-20250514",
"user": "n8n-workflow-{workflow_id}",
"input": [
{ "role": "user", "content": "Workflow result: {data}" }
],
"instructions": "Summarize this result for the user. Do NOT re-trigger the workflow.",
"stream": false,
"timeout_seconds": 0
}
The user string routes to a stable session. Use consistent patterns: n8n-workflow-{id} for per-workflow sessions, n8n-global for shared.
→ Full Gateway API spec, WebSocket protocol, telemetry codes: references/gateway-api.md
1. USER → Agent: "Post Q3 report to #marketing Slack"
2. Agent REASONS: needs Slack API → has no credentials → knows webhook exists
3. Agent TRIGGERS: exec scripts/trigger.sh "slack-send-message" '{"channel":"#marketing"}'
4. n8n EXECUTES: Webhook → Validate → Slack API (locked credentials) → Log
5. n8n RETURNS: HTTP POST to Gateway /v1/responses with result
6. Agent REPORTS: "Posted to #marketing at 2:34 PM"
After the agent generates the workflow structure:
→ Security deep-dive, ClawHavoc analysis, safeguard nodes: references/security.md
Configure exec tool approval in openclaw.json:
{
"exec": {
"enabled": true,
"approval": true
}
}
With approval: true, every shell command requires human confirmation. This prevents a prompt-injected agent from executing arbitrary code.
Add to the agent's soul.md:
## Hard Constraints
- NEVER store, log, or output API keys or webhook secrets in conversation
- NEVER attempt to access n8n management API — only use webhook endpoints
- NEVER execute shell commands that modify system files outside workspace/
- If a webhook fails 3 consecutive times, STOP and alert the user
- NEVER enter infinite retry loops on failed API calls
Configure OpenRouter in openclaw.json to route mundane webhook formatting to cheap models (GPT-4o Mini, Claude 3.5 Haiku) and reserve expensive models for complex reasoning:
{
"reasoning": {
"default_model": "openrouter/gpt-4o-mini",
"complex_model": "openrouter/claude-opus-4-20250514",
"routing": "cost-optimized"
}
}
This reduces API costs by ~70% for repetitive webhook orchestration tasks.
When managing many n8n webhook skills (20+), loading all skill metadata at startup wastes tokens. Use the SkillPointer pattern:
Move individual webhook skills to a vault directory outside the agent's scan path:
~/.openclaw/vault/n8n-webhooks/
Create a single lightweight pointer skill in the active directory:
---
name: n8n-webhook-router
description: "When the user needs to trigger ANY n8n workflow or webhook integration, use the list and read tools to browse ~/.openclaw/vault/n8n-webhooks/ to find and load the exact skill required."
---
This reduces startup token consumption from ~8,000 tokens (40 webhook skills × 200 tokens each) to ~200 tokens (one pointer), while maintaining access to every skill.
→ Docker Compose templates, network topologies, environment variables: references/deployment.md
Quick reference: OpenClaw Gateway defaults to port 18789, n8n to 5678. Never bind the Gateway to public interfaces. Use SSH tunnels or Tailscale for cross-network ingress.
| Symptom | Cause | Fix |
|---|---|---|
| 401 from webhook | Secret mismatch | Verify N8N_WEBHOOK_SECRET matches n8n Header Auth credential |
| 404 from webhook | Inactive workflow or wrong path | Activate workflow; verify naming convention |
| Connection refused | Network unreachable | Check topology, ports, firewall |
| Trigger script hangs | Missing set -euo pipefail | Add strict error handling; ensure no interactive prompts |
| Shell injection | Raw input interpolation | Use urllib.parse.quote or Node.js (no shell expansion) |
| Symptom | Cause | Fix |
|---|---|---|
| 403 from Gateway | Token mismatch | Verify OPENCLAW_GATEWAY_TOKEN |
| Infinite echo loop | Missing timeout_seconds: 0 | Add immediately |
| Session not found | Inconsistent user string | Use stable pattern: n8n-workflow-{id} |
DEVICE_AUTH_SIGNATURE_EXPIRED | Clock skew | Sync NTP across hosts |
| Skill not loading | Invalid YAML frontmatter | Check for multi-line strings; validate with openclaw skills status |
Egress only:
openssl rand -hex 32N8N_WEBHOOK_URL and N8N_WEBHOOK_SECRET set in .envrequires.env and requires.bins declared in YAML frontmatterfiles: ["scripts/*"] declared in frontmatterset -euo pipefail in trigger.shurllib.parse.quote or Node.jscurl before agent triggersAdd bidirectional:
/v1/responses enabled in openclaw.jsonexec.approval: true configured in gatewaysoul.md guardrails writtentimeout_seconds: 0 in all ingress payloadsclawhub CLI, security scan requirements, version managementZIP package — ready to use