Scoping the Agent
The agent receives a new insurance claim via webhook, email, or form — then extracts structured data, classifies the claim, assesses damage and risk using AI, routes it to the right handler, and updates your system of record, all autonomously.
Inputs
Claim JSON from portal, supporting documents (PDFs/images), claimant policy profile, and historical claims data for fraud scoring.
Outputs
Structured claim record, AI-generated summary + severity score, routing decision, claimant notification, and full audit log.
AI Stack
GPT-4o for claim analysis and Vision for document parsing. Optional: embeddings + vector DB for similarity matching.
High-Level Architecture
[TRIGGER: Webhook POST /claims/new]
│
▼
[INTAKE & VALIDATION] ── Set + IF nodes
│
▼
[DOCUMENT EXTRACTION] ──────────► (OpenAI Vision / GPT-4o)
│
▼
[AI CLAIM ANALYSIS] ────────────► (GPT-4o Full Analysis)
│
▼
[FRAUD SCORING] ─────────────────► (Rules Engine / ML API)
│
▼
[ROUTING DECISION — Switch Node]
│
┌────┴────────────┬───────────────┐
▼ ▼ ▼
AUTO-APPROVE ADJUSTER QUEUE FRAUD FLAG
[Pay + Notify] [Assign + Alert] [Freeze + SIU]
│ │ │
└────────┬────────┘ │
▼ │
[STORE CLAIM RECORD] ◄─────────────┘
│
▼
[AUDIT LOG + NOTIFICATIONS]
│
▼
[Webhook Response: 200 OK]Fraud < 20
Damage ≤ $5,000
Uncertain signals
Complex cases
Suspicious flags
SIU escalation
Node-by-Node Walkthrough
NODE 1 — Webhook Trigger Webhook
Purpose: Receives the new claim submission from your portal or external system via HTTP POST.
HTTP Method: POST
Path: /claims/new
Response Mode: "Last Node" # returns confirmation to caller
Authentication: Header Auth # use a secret token
Expected incoming payload:
{
"claim_id": "CLM-2024-00847",
"claimant_name": "Jane Doe",
"policy_number": "POL-99234",
"incident_date": "2024-12-01",
"incident_type": "auto_collision",
"description": "Rear-end collision on Highway 101. Significant trunk damage.",
"documents": ["https://storage.co/photos/CLM-2024-00847/photo1.jpg"],
"estimated_damage": 8400
}
NODE 2 — Normalize & Enrich Set
Purpose: Standardize the incoming payload and add metadata fields before any processing begins.
claim_id: {{ $json.claim_id }}
received_at: {{ new Date().toISOString() }}
status: "processing"
incident_type: {{ $json.incident_type }}
description: {{ $json.description }}
claimant_name: {{ $json.claimant_name }}
policy_number: {{ $json.policy_number }}
documents: {{ $json.documents }}
estimated_damage: {{ $json.estimated_damage }}
NODE 3 — Fetch Policy Details HTTP Request
Purpose: Pull the claimant's policy data from your internal API or database to validate coverage before proceeding.
Method: GET
URL: https://your-policy-api.com/policies/{{ $json.policy_number }}
Auth: Bearer Token # add in n8n Credentials
Headers:
Content-Type: application/json
Extract via Set node after:
coverage_limit: {{ $json.coverage_limit }}
deductible: {{ $json.deductible }}
policy_active: {{ $json.is_active }}
covered_incidents: {{ $json.covered_types }}
NODE 4 — Policy Validation Check IF
Purpose: Immediately reject invalid or inactive policies before wasting AI API calls.
# Both conditions must be TRUE to proceed
{{ $json.policy_active }} equals true
AND
{{ $json.covered_incidents }} contains {{ $json.incident_type }}
→ TRUE: Continue to AI Analysis
→ FALSE: Send rejection email + log claim
NODE 5 — Document OCR / Vision Extraction HTTP Request
Purpose: Extract text and damage information from uploaded photos or PDFs using OpenAI Vision (GPT-4o).
{
"model": "gpt-4o",
"messages": [{
"role": "user",
"content": [
{
"type": "text",
"text": "You are an insurance claims assessor. Analyze this damage photo and extract: 1) Type of damage 2) Estimated severity (low/medium/high/total-loss) 3) Visible damage description 4) Any suspicious indicators. Return as JSON."
},
{
"type": "image_url",
"image_url": { "url": "{{ $json.documents[0] }}" }
}
]
}],
"max_tokens": 800
}
Parse response in Code node immediately after:
const content = $input.first().json.choices[0].message.content;
// Strip markdown code fences if present
const cleaned = content.replace(/```json|```/g, '').trim();
const parsed = JSON.parse(cleaned);
return [{
json: {
...$input.first().json,
vision_damage_type: parsed.damage_type,
vision_severity: parsed.severity,
vision_description: parsed.description,
vision_flags: parsed.suspicious_indicators
}
}];
NODE 6 — Core Claim Analysis OpenAI
Purpose: The brain of the agent — full AI analysis, claim summary, severity score, fraud score, and routing recommendation.
System Prompt:
You are a senior P&C insurance claims analyst AI. Your job is to:
1. Analyze claim details and document findings
2. Generate a professional claim summary (2-3 sentences)
3. Assign a severity score 1-10 (10 = most severe)
4. Estimate a fair payout range based on the policy and damage
5. Identify any fraud indicators (score 0-100, 100 = highly suspicious)
6. Recommend routing: "AUTO_APPROVE", "ADJUSTER_REVIEW", or "FRAUD_INVESTIGATION"
Routing rules:
- AUTO_APPROVE: severity <= 4, fraud_score < 20, damage <= $5,000
- FRAUD_INVESTIGATION: fraud_score >= 60
- ADJUSTER_REVIEW: everything else
Always respond in valid JSON only. No extra text.
User Message (dynamic n8n expression):
Claim ID: {{ $json.claim_id }}
Incident Type: {{ $json.incident_type }}
Description: {{ $json.description }}
Estimated Damage: ${{ $json.estimated_damage }}
Coverage Limit: ${{ $json.coverage_limit }}
Deductible: ${{ $json.deductible }}
Vision Analysis: {{ $json.vision_description }}
Severity (Vision): {{ $json.vision_severity }}
Suspicious Flags: {{ $json.vision_flags }}
Incident Date: {{ $json.incident_date }}
Expected JSON response from GPT:
{
"summary": "Claimant reports rear-end collision causing significant trunk damage...",
"severity_score": 6,
"fraud_score": 12,
"estimated_payout_min": 7200,
"estimated_payout_max": 8800,
"routing": "ADJUSTER_REVIEW",
"notes": "Damage aligns with described incident. No major fraud indicators."
}
Parse in Code node after:
const raw = $input.first().json.message?.content ||
$input.first().json.choices?.[0]?.message?.content;
const cleaned = raw.replace(/```json|```/g, '').trim();
const ai = JSON.parse(cleaned);
return [{
json: {
...$input.first().json,
ai_summary: ai.summary,
severity_score: ai.severity_score,
fraud_score: ai.fraud_score,
payout_min: ai.estimated_payout_min,
payout_max: ai.estimated_payout_max,
routing: ai.routing,
ai_notes: ai.notes
}
}];
NODE 7 — Routing Decision Switch
Purpose: Branch the workflow into three distinct paths based on the AI's routing recommendation.
Value to switch on: {{ $json.routing }}
Route 1: AUTO_APPROVE
Route 2: ADJUSTER_REVIEW
Route 3: FRAUD_INVESTIGATION
Default: ADJUSTER_REVIEW
NODE 8A — Auto Approve Branch Code HTTP
8A-1: Calculate Final Payout
const deductible = $json.deductible || 0;
const midpoint = ($json.payout_min + $json.payout_max) / 2;
const final_payout = Math.max(0, midpoint - deductible);
return [{ json: { ...$json, final_payout, approval_status: "APPROVED" } }];
8A-2: Trigger Payment API
// POST https://your-claims-system.com/api/payments
{
"claim_id": "{{ $json.claim_id }}",
"amount": {{ $json.final_payout }},
"policy": "{{ $json.policy_number }}"
}
8A-3: Email Claimant
To: {{ $json.claimant_email }}
Subject: Your Claim {{ $json.claim_id }} Has Been Approved
Hi {{ $json.claimant_name }},
Your claim has been approved. A payout of
${{ $json.final_payout }} will be processed within 2 business days.
Summary: {{ $json.ai_summary }}
NODE 8B — Adjuster Review Branch HTTP
8B-1: Create Ticket (Jira / ServiceNow / Salesforce)
{
"title": "Claim Review: {{ $json.claim_id }}",
"priority": "{{ $json.severity_score > 7 ? 'HIGH' : 'NORMAL' }}",
"description": "{{ $json.ai_summary }}",
"estimated_payout": "${{ $json.payout_min }} – ${{ $json.payout_max }}",
"assigned_queue": "claims_adjusters"
}
8B-2: Slack Alert to Adjuster Team
Channel: #claims-queue
🔍 *New Claim for Review*
Claim: {{ $json.claim_id }} | Severity: {{ $json.severity_score }}/10
Claimant: {{ $json.claimant_name }}
Est. Payout: ${{ $json.payout_min }}–${{ $json.payout_max }}
AI Notes: {{ $json.ai_notes }}
NODE 8C — Fraud Investigation Branch Set
8C-1: Flag the Claim
fraud_flagged: true
freeze_payment: true
investigation_status: "OPEN"
8C-2: Immediate Fraud Alert to Slack
Channel: #fraud-alerts
🚨 *FRAUD ALERT — Claim {{ $json.claim_id }}*
Fraud Score: {{ $json.fraud_score }}/100
Claimant: {{ $json.claimant_name }}
Vision Flags: {{ $json.vision_flags }}
⚠️ Immediate review required — payment frozen.
NODE 9 — Store Claim Record Google Sheets
Purpose: Persist every claim with full AI analysis for audit, reporting, and future model retraining.
Operation: Append Row
Sheet: Claims_Log
Column Mapping:
A: claim_id B: received_at C: claimant_name
D: policy_number E: incident_type F: severity_score
G: fraud_score H: routing I: final_payout
J: ai_summary K: status L: vision_severity
NODE 10 — Error Handling Code
Step 1: On any node → Settings → "On Error" → set to "Continue (using error output)"
Step 2: Add an Error Trigger node at the workflow level.
Step 3: Format error in Code node:
const error = $input.first().json;
return [{
json: {
workflow: "P&C Claims Agent",
error_message: error.error?.message || "Unknown error",
node: error.node?.name,
claim_id: error.execution?.data?.claim_id || "UNKNOWN",
timestamp: new Date().toISOString()
}
}];
Step 4: Route to #errors Slack channel and log to a Google Sheets error tab.
NODE 11 — Retry Logic Code + Wait
const retryCount = $json.retry_count || 0;
if (retryCount >= 3) {
throw new Error(`Max retries reached for claim ${$json.claim_id}`);
}
return [{
json: {
...$json,
retry_count: retryCount + 1
}
}];
retry_count.Memory & Data Persistence
Short-Term Memory: n8n automatically passes data between nodes. Use Set nodes liberally to carry all needed fields forward through every branch.
Option A — Redis (Fast Key-Value) HTTP Request
# Store fraud patterns and past decisions per policy
Key: "claim_history:{{ $json.policy_number }}"
Value: JSON.stringify(claimRecord)
TTL: 2592000 # 30 days in seconds
Option B — Pinecone / Qdrant (Vector DB) HTTP Request
# Step 1 — Embed the claim description
POST https://api.openai.com/v1/embeddings
{ "input": "{{ $json.description }}", "model": "text-embedding-3-small" }
# Step 2 — Query Pinecone for top-5 similar past claims
POST https://your-index.pinecone.io/query
{ "vector": [...embedding], "topK": 5, "includeMetadata": true }
# Step 3 — Inject similar claims into GPT prompt for RAG
Authentication Setup
| Service | Auth Type | n8n Setup Path |
|---|---|---|
| OpenAI | API Key | Credentials → OpenAI API → paste key |
| Google Sheets | OAuth2 | Credentials → Google Sheets OAuth2 |
| Slack | OAuth2 | Credentials → Slack OAuth2 |
| Policy Internal API | Bearer Token | Header Auth → Authorization: Bearer <token> |
| Pinecone | API Key | HTTP Header Auth → Api-Key: <key> |
| Salesforce / CRM | OAuth2 | Credentials → Salesforce OAuth2 |
{{ $credentials.apiKey }}.Complete Workflow Summary
[Webhook: POST /claims/new]
│
[Set: Normalize fields]
│
[HTTP: Fetch Policy from API]
│
[IF: Policy valid + covered?]
│ │
[YES] [NO → Email Rejection + Log]
│
[HTTP: OpenAI Vision → Damage Analysis]
│
[Code: Parse Vision JSON]
│
[OpenAI: Full Claim Analysis]
│
[Code: Parse AI JSON + enrich fields]
│
[Switch: {{ $json.routing }}]
│ │ │
[AUTO] [ADJUSTER] [FRAUD]
│ │ │
[Calc [CRM Ticket] [Freeze +
Payout] [Slack Alert] SIU Alert]
[Pay API]
[Email]
│
[ALL PATHS MERGE]
│
[Google Sheets: Append Claim Log]
│
[Webhook Response: 200 OK + { claim_id }]Making the Agent More Powerful
Chain Sub-Workflows
Split into modular Execute Workflow nodes: Document Extraction, Fraud Analysis, Payout Calc, Notifications — each independently testable.
Multi-Channel Intake
IMAP email trigger, Twilio SMS, WhatsApp webhook, and mobile app POST — all routing into the same processing pipeline.
Persistent AI Memory
Supabase + pgvector or Pinecone to detect serial filers, benchmark payouts, and feed RAG context into GPT.
Live Dashboard
Connect Google Sheets to Looker Studio or push to Supabase — track resolution time, fraud rate, auto-approval rate.
Human-in-the-Loop
Email adjuster an Approve/Reject link → Wait node pauses until clicked → resume with their decision token.
PII Masking
Code node before every OpenAI call redacts claimant names, masks policy numbers — keeps sensitive data out of AI logs.
PII Masking — Copy-Paste Code Code
// Run this Code node BEFORE any OpenAI call
const masked = {
...$json,
claimant_name: "REDACTED",
policy_number: $json.policy_number.replace(/.(?=.{4})/g, '*')
};
return [{ json: masked }];
Scheduled Reprocessing Cron
# Cron trigger use cases:
→ Re-run stale claims in ADJUSTER_REVIEW > 48 hours
→ Generate daily fraud pattern summary reports
→ Sync claim statuses from external systems every 4 hours
Quick-Start Build Order
Build in this order — validate each stage before layering in the next.
- Set up n8n instance (cloud or self-hosted)
- Configure OpenAI API credentials in n8n Credentials vault
- Configure Google Sheets OAuth2
- Configure Slack OAuth2
- Build and test Node 1–4 (intake + validation loop)
- Add OpenAI Vision (Node 5) with a real damage photo
- Add core GPT-4o analysis (Node 6) — verify JSON output
- Build all three routing branches (Node 8A / 8B / 8C)
- Set up Google Sheets claim log (Node 9)
- Add error handling to every HTTP / AI node (Node 10)
- Test full end-to-end flow with a mock claim payload
- Add retry logic for flaky external APIs (Node 11)
- Add PII masking Code node before all OpenAI calls
- Load test and monitor before going to production