Automated Webhook Ticket Triage: Schema Guard, Dead-Letter Fallback, Cost & Permissions
A practical customer support / issue triage pipeline comparing local Ollama vs cloud API cost, strict input truncation, and least-privilege credential scoping.
Docker n8n 1.76+ · Compatible with Local Ollama or Any OpenAI-Compatible Endpoint
When handling PII customer tickets, point API_BASE_URL to local Ollama (`http://host.docker.internal:11434/v1`) so customer data never leaves your server.
- Unbounded user webhook bodies can inject 50K+ tokens or prompt-injection payloads if not sliced (`slice(0, 2000)`) before the LLM node.
- Giving the workflow full Read/Write admin API keys to downstream CRM/Slack exposes the system if a webhook secret is leaked.
1. Input Sanitization, Cost Cap & Least-Privilege Scoping
| Deployment Mode | Monthly Cost (30K Tickets) | Avg Latency / Ticket | PII / Data Boundary |
|---|---|---|---|
| Local Ollama (Qwen2.5-7B Q4_K_M) | $0 API (~$0.80 electricity) | 1.4s (RTX 4070 / M3 Pro) | 100% On-Premise LAN |
| Budget Cloud API (gpt-4o-mini / Haiku) | ~$4.50 – $9.00 / month | 0.9s (Network dependent) | Sent to 3rd-party cloud |
| Flagship Cloud API (Sonnet / GPT-4o) | ~$85.00 – $140.00 / month | 1.8s – 3.2s | Sent to 3rd-party cloud |
curl -X POST http://localhost:5678/webhook-test/tinker-ticket-triage \
-H "Content-Type: application/json" \
-d '{
"ticketId": "TCK-9042",
"message": "Our Docker container on Ubuntu 24.04 gets Connection Refused when calling host.docker.internal:11434 after upgrading Ollama."
}'2. Dead-Letter Queue Fallback for Unparseable or Timed-Out Tickets
3. Defending Against Indirect Prompt Injection in User Payloads
Frequently Asked Questions & Edge Cases
Q: How do I prevent prompt injection inside user-submitted support tickets?
Wrap the untrusted user text inside explicit XML delimiters (`<user_ticket>...</user_ticket>`), instruct the system prompt to treat anything inside `<user_ticket>` strictly as passive data, and validate the output JSON against a fixed enum (`low | medium | high`) in an n8n Code/Switch node.
Q: Should the Webhook node return HTTP 200 immediately or wait for the LLM to finish?
If upstream callers (e.g., Stripe, GitHub, Zendesk) enforce a strict 3–5 second webhook timeout, configure the n8n Webhook node to `Respond: Immediately` (HTTP 200 ACK) after verifying the HMAC signature, and process the local LLM classification asynchronously.
Webhook Ticket Triage Workflow with Schema Guard & Dead-Letter Fallback
Validates incoming payload, enforces strict JSON output, caps token cost, and routes malformed responses to a safe fallback queue.
{
"name": "Tinker AI — Webhook Ticket Triage with Schema Guard",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "tinker-ticket-triage",
"responseMode": "lastNode"
},
"name": "Inbound Ticket Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [200, 280]
},
{
"parameters": {
"method": "POST",
"url": "http://host.docker.internal:11434/v1/chat/completions",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ model: 'qwen2.5:7b', temperature: 0, max_tokens: 256, response_format: { type: 'json_object' }, messages: [{ role: 'system', content: 'Return JSON with keys: urgency (low|medium|high), department (billing|tech|general), reason (string).' }, { role: 'user', content: String($json.body?.message || '').slice(0, 2000) }] }) }}",
"options": { "timeout": 30000 }
},
"name": "Structured Triage LLM",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"retryOnFail": true,
"maxTries": 2,
"continueOnFail": true,
"position": [460, 280]
}
],
"connections": {
"Inbound Ticket Webhook": {
"main": [[{ "node": "Structured Triage LLM", "type": "main", "index": 0 }]]
}
}
}