n8n agentic webhook with Claude
Trigger an LLM workflow from any webhook — drop-in node config plus the prompt that actually works.
n8n + Claude is the fastest way to put an LLM behind any business event — Stripe charge, Calendly booking, GitHub PR opened, support email landed. The trick is keeping the Anthropic call deterministic enough to wire into approval chains.
The workflow
Webhook → Set (build prompt) → Anthropic (Claude) → Switch (route) → [Slack | Gmail | DB]
Anthropic node config
| Field | Value |
|---|---|
| Model | claude-opus-4-7 |
| Operation | Message |
| System | Pasted from below; mark cache_control: ephemeral if your n8n version supports it |
| Tool choice | none (we want pure JSON) |
| Max tokens | 512 |
| Temperature | 0.2 |
System prompt
You triage incoming support emails. Output JSON ONLY:
{
"category": "billing" | "bug" | "feature_request" | "refund" | "other",
"urgency": "low" | "medium" | "high",
"summary": string (max 200 chars),
"suggested_reply": string (max 600 chars, friendly tone),
"needs_human": boolean
}
Set needs_human=true for: refunds > $500, legal threats, security issues, anything outside the categories.
Switch node
Three outputs based on {{ $json.message.content[0].text }} parsed:
needs_human === true→ Slack to#support-escalationscategory === "bug"→ GitHub Issue (via the GitHub node)- otherwise → Gmail draft with
suggested_reply
Patterns that scale
- Pin a model version.
claude-opus-4-7notclaude-3-5-sonnet. Workflow drift from auto-bumped models is a real issue. - Always parse JSON in a Function node before Switch. n8n's expression engine choking on stringified JSON is a top cause of false routes.
- Add a Wait node between LLM and side-effects in approval chains. Lets a human catch a bad classification.
- Log usage to a Postgres node.
input_tokens,cache_read_input_tokens,output_tokens— track these per workflow ID; you'll find the runaway loop on day three.
How it works
The workflow is a straight line of n8n nodes: a Webhook node receives the event, a Set node assembles the prompt, the Anthropic node calls Claude, a Switch node routes the result, and the final nodes perform side effects. The design goal is to make the model's output boring and predictable so it can be trusted inside an approval chain.
The Anthropic node config is where that predictability comes from. The system prompt does the heavy lifting: it tells the model to output JSON only, and it spells out the exact shape — category, urgency, summary, suggested_reply, and needs_human — including the allowed enum values and length caps. The needs_human flag is the safety valve, set true for refunds over $500, legal threats, security issues, and anything that doesn't fit the categories. The config keeps temperature low (0.2) for stable output, caps max_tokens at 512 because the response is small, and sets tool choice to none so the model returns a plain message rather than calling a tool. The Switch node then reads the parsed fields — needs_human first, then category === "bug", then a catch-all — and sends each branch to Slack, a GitHub issue, or a Gmail draft.
Notes & gotchas
The most common failure here is JSON parsing. n8n's expression engine works on objects, so parse the model's text into real JSON in a Function node before the Switch reads from it — feeding it a stringified blob is the top cause of false routes. Reference the model output through the message content path ({{ $json.message.content[0].text }}) and parse that.
Two operational notes the patterns above stress. Pin the model version explicitly rather than letting it auto-bump, because a silently upgraded model can shift classification behavior and quietly break your routing. And drop a Wait node between the LLM and any irreversible side effect in an approval chain, so a human can catch a bad classification before an email is sent or an issue is filed. Finally, log token usage per workflow to Postgres; tracking input, cached-read, and output tokens is how you catch a runaway loop before it gets expensive. Treat the Anthropic API key as a server-side credential stored in n8n's credential store, never inlined into a node or exposed to the webhook caller.