AutomationFlowsAI & RAG › Detect Fraud in User Activity with Postgresql, Openai and Slack

Detect Fraud in User Activity with Postgresql, Openai and Slack

ByWeblineIndia @weblineindia on n8n.io

> n8n + PostgreSQL + OpenAI + Slack

Webhook trigger★★★★☆ complexityAI-powered18 nodesPostgresOpenAISlack
AI & RAG Trigger: Webhook Nodes: 18 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #15419 — we link there as the canonical source.

This workflow follows the OpenAI → Postgres recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "NO1SYMzjYaj49WP5",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Fraud Detection Workflow",
  "tags": [],
  "nodes": [
    {
      "id": "4e594811-2154-409e-8022-0f3fd53cc448",
      "name": "User Activity Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1776,
        0
      ],
      "parameters": {
        "path": "user-activity",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 1
    },
    {
      "id": "122b6f09-9e26-4ee0-ab92-b414347babf3",
      "name": "Request Validator",
      "type": "n8n-nodes-base.code",
      "position": [
        -1584,
        0
      ],
      "parameters": {
        "jsCode": "const body = $json.body;\n\nif (!body) {\n  throw new Error(\"Missing body in webhook\");\n}\n\nreturn [{\n  json: {\n    user_id: body.user_id,\n    event: body.event,\n    ip: body.ip,\n    location: body.location,\n    device: body.device\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "4fca64af-ee73-4ed0-8bb2-467fc300dcc9",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1856,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 464,
        "height": 464,
        "content": "## Incoming Request Handling\nThis section receives raw user activity data via webhook and validates the payload structure. It ensures required fields like user_id, event, device and location exist before passing data into the fraud detection pipeline."
      },
      "typeVersion": 1
    },
    {
      "id": "b01ac203-309e-4b63-a46a-64f802c36287",
      "name": "Fetch Recent User Logs",
      "type": "n8n-nodes-base.postgres",
      "position": [
        -1264,
        0
      ],
      "parameters": {
        "query": "SELECT *\nFROM user_activity_logs\nWHERE user_id = '{{ $json.user_id }}'\nORDER BY created_at DESC\nLIMIT 10;",
        "operation": "executeQuery",
        "additionalFields": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "a69ec751-8666-4c2c-a346-7d3a2b6e7020",
      "name": "Context Builder",
      "type": "n8n-nodes-base.code",
      "position": [
        -1056,
        0
      ],
      "parameters": {
        "jsCode": "// const current =$('Request Validator').first().json;\n// const history = items ? items?.map(i => i.json) : [];\n\n// return [{\n//   json: {\n//     ...current,\n//     history\n//   }\n// }];\n\nconst current = $('Request Validator').first().json;\n\nconst history = items\n  .map(i => i.json)\n  .filter(r => r && r.user_id); // only real records\n\nreturn [{\n  json: {\n    ...current,\n    history: history.length ? history : []\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "aa5fd4a8-a02d-4f91-8ba4-8d654a5a24ed",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1360,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 464,
        "content": "## User Context Enrichment\nThis section retrieves the last 10 user activity records from the database and merges them with the current event. It builds behavioral context so both rule-based and AI models can detect anomalies like device changes or suspicious location shifts."
      },
      "typeVersion": 1
    },
    {
      "id": "792992cf-d4f5-49ef-a573-c435ba47be26",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -816,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 464,
        "content": "## Rule-Based Fraud Detection\nThis section applies deterministic fraud detection logic including device novelty, impossible travel detection, geographic anomalies and sensitive action scoring. It produces a risk score and preliminary risk classification (LOW/MEDIUM/HIGH) before AI analysis is applied."
      },
      "typeVersion": 1
    },
    {
      "id": "a27f0df3-052e-4d2d-b1df-bdc849f0aabc",
      "name": "Fraud Rules Engine",
      "type": "n8n-nodes-base.function",
      "position": [
        -672,
        0
      ],
      "parameters": {
        "functionCode": "const event = $json;\nconst history = $json.history || [];\nlet score = 0;\nlet reasons = [];\n\n// 1. REAL DEVICE CHECK: Is this specific string new for THIS user?\nconst knownDevices = history.map(h => h.device);\nif (history.length > 0 && !knownDevices.includes(event.device)) {\n    score += 3;\n    reasons.push(\"First time using this device\");\n}\n\n// 2. IMPOSSIBLE TRAVEL CHECK\nif (history.length > 0) {\n    const last = history[0];\n    const timeDiffHours = Math.abs(new Date(event.timestamp || Date.now()) - new Date(last.created_at)) / 36e5;\n    \n    if (last.location !== event.location && timeDiffHours < 4) {\n        score += 7; // Auto-High\n        reasons.push(\"Impossible Travel: Location changed too fast\");\n    }\n}\n\n// 3. GEOGRAPHIC ANOMALY\nif (event.location !== \"India\") {\n    score += 3;\n    reasons.push(\"Access from foreign country\");\n}\n\n// 4. ACTION SENSITIVITY\nif (event.event === \"funds_withdrawal\" || event.event === \"password_change\") {\n    score += 2;\n    reasons.push(\"Sensitive operation\");\n}\n\n// FINAL VERDICT\nlet risk = \"LOW\";\nif (score >= 7) risk = \"HIGH\";\nelse if (score >= 3) risk = \"MEDIUM\";\n\nreturn [{\n    json: {\n        ...event,\n        rule_score: score,\n        rule_risk: risk,\n        risk_reasons: reasons.join(\", \")\n    }\n}];"
      },
      "typeVersion": 1
    },
    {
      "id": "b3b60c52-22d8-46ce-b45c-9d6d9c18fc8f",
      "name": "AI Fraud Interpreter",
      "type": "n8n-nodes-base.openAi",
      "position": [
        -304,
        0
      ],
      "parameters": {
        "model": "gpt-4o",
        "prompt": {
          "messages": [
            {
              "role": "system",
              "content": "=You are a fraud detection assistant.\n\nYou do NOT calculate scores.\nYou ONLY interpret the given rule-based output.\n\nReturn JSON ONLY in this format:\n\n{\n  \"risk_level\": \"LOW | MEDIUM | HIGH\",\n  \"reason\": \"short explanation\"\n}"
            },
            {
              "content": "=Analyze this login event:\n\nUser Event:\n{{ JSON.stringify($json) }}\n\nRule Score: {{ $json.rule_score }}\nRule Risk: {{ $json.rule_risk }}\n\nHistory Count: {{ $json.history.length }}"
            }
          ]
        },
        "options": {},
        "resource": "chat",
        "requestOptions": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3e8006d3-8bc1-4474-88b0-706c8ae8bf85",
      "name": "AI Response Cleaner",
      "type": "n8n-nodes-base.code",
      "position": [
        -112,
        0
      ],
      "parameters": {
        "jsCode": "let content = $json.message.content;\n\n// remove markdown wrapper\ncontent = content\n  .replace(/```json/g, \"\")\n  .replace(/```/g, \"\")\n  .trim();\n\n// parse JSON safely\nlet ai;\ntry {\n  ai = JSON.parse(content);\n} catch (e) {\n  throw new Error(\"AI response parsing failed: \" + content);\n}\n\n// return merged object (IMPORTANT: preserve existing data)\nreturn [{\n  json: {\n    ...$json,\n    ai_risk: ai.risk_level,\n    ai_reason: ai.reason\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "31875210-c22b-42f1-831e-8f097cfc9ad6",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 464,
        "content": "## AI Risk Interpretation\nThis section sends structured rule outputs and user context to an AI model. The AI does not calculate risk but interprets signals and returns a risk level with a short human-readable explanation."
      },
      "typeVersion": 1
    },
    {
      "id": "918921f1-485c-464b-8eb4-645dc987095f",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        112,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 464,
        "content": "## Decision Fusion Layer\nThis section combines rule-based and AI-based results into a final unified fraud decision. It prioritizes high-risk signals from either system and generates the final risk classification used for alerts and logging."
      },
      "typeVersion": 1
    },
    {
      "id": "8228af43-76d7-4475-b421-675fcf0a44cd",
      "name": "Risk Decision Combiner",
      "type": "n8n-nodes-base.code",
      "position": [
        240,
        0
      ],
      "parameters": {
        "jsCode": "const webhookData = $('Fraud Rules Engine').first().json\nconst aiData = $input.first().json\n\nreturn [{\n  json: {\n    user_id: webhookData.user_id,\n    event: webhookData.event,\n    ip: webhookData.ip,\n    location: webhookData.location,\n    device: webhookData.device,\n    history: webhookData.history || [],\n    rule_score: webhookData.rule_score,\n    rule_risk: webhookData.rule_risk,\n    ai_risk: aiData.ai_risk,\n    ai_reason: aiData.ai_reason,\n    final_risk:\n      (webhookData.rule_risk === \"HIGH\" || aiData.ai_risk === \"HIGH\")\n        ? \"HIGH\"\n        : (webhookData.rule_risk === \"MEDIUM\" || aiData.ai_risk === \"MEDIUM\")\n          ? \"MEDIUM\"\n          : \"LOW\"\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "dafe39a2-ada7-42f5-97eb-3dbd17574e94",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        528,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 768,
        "height": 464,
        "content": "## Persistence & Alerting\n\nThis section stores the final fraud decision in the database and triggers real-time alerts for high-risk cases via Slack. It ensures suspicious activity is logged and immediately escalated for human review."
      },
      "typeVersion": 1
    },
    {
      "id": "4795e7c1-1d8e-4a51-a18c-284578ff6003",
      "name": "Database Logger",
      "type": "n8n-nodes-base.postgres",
      "position": [
        624,
        0
      ],
      "parameters": {
        "query": "INSERT INTO user_activity_logs\n(\n  user_id,\n  event,\n  ip,\n  location,\n  device,\n  risk_score,\n  ai_flag\n)\nVALUES (\n  '{{ $json.user_id }}',\n  '{{ $json.event }}',\n  '{{ $json.ip }}',\n  '{{ $json.location }}',\n  '{{ $json.device }}',\n  {{ $json.rule_score }},\n  '{{ $json.ai_risk }}'\n)\nRETURNING *;",
        "operation": "executeQuery",
        "additionalFields": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4bedc062-08af-4235-8159-ca88af370c8d",
      "name": "High Risk Filter",
      "type": "n8n-nodes-base.if",
      "position": [
        832,
        0
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $('Risk Decision Combiner').item.json.final_risk }}",
              "value2": "=HIGH"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1bbe1d2b-a7df-4d0d-9752-178d583db7f2",
      "name": "Slack Alert Dispatcher",
      "type": "n8n-nodes-base.slack",
      "position": [
        1056,
        -96
      ],
      "parameters": {
        "text": "=FRAUD ALERT  \nUser: {{$json.user_id}}\nEvent: {{$json.event}} \nRisk: {{ $('Risk Decision Combiner').item.json.final_risk }}\nScore: {{ $('Risk Decision Combiner').item.json.rule_score }}\nAI Reason: {{ $('Risk Decision Combiner').item.json.ai_reason }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0ALCNTB03G",
          "cachedResultName": "complain"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "fe4509da-bdfd-44ee-a7a2-729588cd78c2",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1856,
        -848
      ],
      "parameters": {
        "width": 784,
        "height": 512,
        "content": "## Workflow Overview & Setup Guide\n\nThis workflow is an AI-assisted fraud detection pipeline that evaluates user activity in real time. It starts when a webhook receives a user event such as login, password change or transaction activity. The system first validates the input to ensure all required fields are present.\n\nNext, it fetches the user\u2019s recent activity history from PostgreSQL and merges it with the current event. This contextual data is critical for detecting behavioral anomalies like unusual device usage or rapid geographic changes.\n\nA rule-based engine then calculates an initial fraud score using deterministic logic such as device mismatch, impossible travel and sensitive actions. This produces a baseline risk level.\n\nThe same enriched data is then sent to an AI model, which interprets the rule output and provides a human-readable risk assessment. Both AI and rule-based outputs are merged into a final decision layer that determines the overall fraud risk.\n\nFinally, all events are stored in the database. If the risk is HIGH, a Slack alert is triggered for immediate investigation.\n\n**Setup Requirements:**\n- Webhook endpoint for event ingestion\n- PostgreSQL table user_activity_logs\n- OpenAI API credentials\n- Slack bot integration\n- Proper field mapping: user_id, event, ip, location, device"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "6e3234f4-e186-4824-9fae-2da23b8e4434",
  "connections": {
    "Context Builder": {
      "main": [
        [
          {
            "node": "Fraud Rules Engine",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Database Logger": {
      "main": [
        [
          {
            "node": "High Risk Filter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "High Risk Filter": {
      "main": [
        [
          {
            "node": "Slack Alert Dispatcher",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request Validator": {
      "main": [
        [
          {
            "node": "Fetch Recent User Logs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fraud Rules Engine": {
      "main": [
        [
          {
            "node": "AI Fraud Interpreter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Response Cleaner": {
      "main": [
        [
          {
            "node": "Risk Decision Combiner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Fraud Interpreter": {
      "main": [
        [
          {
            "node": "AI Response Cleaner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "User Activity Webhook": {
      "main": [
        [
          {
            "node": "Request Validator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Recent User Logs": {
      "main": [
        [
          {
            "node": "Context Builder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Risk Decision Combiner": {
      "main": [
        [
          {
            "node": "Database Logger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

&gt; n8n + PostgreSQL + OpenAI + Slack

Source: https://n8n.io/workflows/15419/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Pyragogy AI Village - Orchestrazione Master (Architettura Profonda V2). Uses start, postgres, openAi, emailSend. Webhook trigger; 36 nodes.

Start, Postgres, OpenAI +4
AI & RAG

Pyragogy AI Village - Orchestrazione Master (Architettura Profonda V2). Uses start, postgres, openAi, emailSend. Webhook trigger; 35 nodes.

Start, Postgres, OpenAI +3
AI & RAG

Eu Clara – Funil Kiwify Completo. Uses postgres, openAi, httpRequest, gmail. Webhook trigger; 70 nodes.

Postgres, OpenAI, HTTP Request +1
AI & RAG

Lua Nova - Sistema Completo. Uses postgres, httpRequest, openAi. Webhook trigger; 55 nodes.

Postgres, HTTP Request, OpenAI
AI & RAG

User Signup & Verification: The workflow starts when a user signs up. It generates a verification code and sends it via SMS using Twilio. Code Validation: The user replies with the code. The workflow

Postgres, HTTP Request, OpenAI +2