Enriching Splunk IP Addresses with n8n and AI in Real Time

When you’re hunting in Splunk and see an IP address in your logs, you want to know: who is this, and should I care? Manually looking it up in a threat intel feed takes you out of the flow. What if Splunk could enrich that IP automatically β€” in real time β€” using free tools?

This guide walks through building a real-time IP enrichment pipeline using Splunk webhook β†’ n8n workflow β†’ AI analysis β†’ threat intelligence lookup, all with free/open-source tools.

The Problem

You’re looking at a log search in Splunk. You see this IP address in your data:

185.220.101.42

Your brain goes: β€œHmm, should I care?” You tab over to VirusTotal, paste it in, wait for results. Five minutes gone. In that time, the incident has progressed.

What if you could get that enrichment before you even click on the IP?

The Architecture

Splunk (alert/webhook)
    ↓
n8n webhook trigger
    ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Enrichment Pipeline         β”‚
β”‚                              β”‚
β”‚  1. IP geolocation lookup    β”‚
β”‚  2. Threat intel lookup      β”‚
β”‚  3. AI analysis of results   β”‚
β”‚  4. Return enriched data     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    ↓
Splunk (enriched search results)

Step 1: Set Up the n8n Webhook

  1. Open your n8n instance and create a new workflow
  2. Add a Webhook node
    • Set HTTP method to POST
    • Set the webhook URL (you’ll get a unique URL)
    • Enable β€œRespond to Webhook” so n8n can return data
  3. Save and activate the workflow

Step 2: IP Geolocation Lookup

Add an HTTP Request node after the webhook:

{
  "ip": "="
}

Use a free geolocation API. Options:

  • ip-api.com (free, no API key needed, 45 req/min)
  • ipgeolocation.io (free tier, API key required)
  • ipapi.co (free tier, 30K requests/month)

Example ip-api.com call:

GET https://ip-api.com/json/?fields=status,message,country,regionName,city,isp,org,as,mobile,proxy,hosting,query

Step 3: Threat Intelligence Lookup

Add another HTTP Request node for threat intel. Free options:

  • VirusTotal (free tier: 50 req/day, API key required)
  • AbuseIPDB (free tier: 100 req/day, API key required)
  • Shodan (free tier: 1 req/min, API key required)

Example AbuseIPDB:

GET https://api.abuseipdb.com/api/v2/check
Headers: Key: 
Query: ip=&maxAgeInDays=90

Step 4: AI Analysis

Add an AI Agent or HTTP Request node to call an AI model:

{
  "prompt": "Based on the following IP enrichment data, assess whether this IP is suspicious. Return a concise risk assessment.\n\nIP: \nGeolocation: , \nISP: \nThreat Score: /100\nReports: \n\nReturn: risk_level (low/medium/high/critical), summary, and recommended action."
}

You can use:

  • Ollama (local, free) β€” see the AI Security Toolchain post for setup
  • LM Studio (local, free)
  • OpenRouter (pay-per-use, many models)

Step 5: Return Enriched Data to Splunk

Configure the final node to return JSON:

{
  "ip": "",
  "risk_level": "",
  "summary": "",
  "country": "",
  "city": "",
  "isp": "",
  "threat_score": ""
}

Step 6: Trigger from Splunk

There are two ways to connect Splunk to your n8n webhook: alert-driven (push) or lookup-driven (pull). I’ll cover both.

Method A: Alert-Driven (Splunk pushes to n8n)

This is the simplest approach. Splunk sends an HTTP POST to your n8n webhook whenever a search matches.

  1. Create a saved search with the query you want to monitor:
    index=* | rex field=_raw "(?P<ip_address>\d+\.\d+\.\d+\.\d+)" | stats count by ip_address
    

    (Adjust the regex to match your actual IP field name.)

  2. Set up the alert:
    • Click Save As β†’ Alert
    • Set the alert condition (e.g., β€œResults > 0”)
    • Under Alert action, choose Webhook (if the Webhook action is installed) or Custom script
  3. If Webhook action is not available (Splunk Free doesn’t include it by default), use a Custom Script action:

    Create a script at $SPLUNK_HOME/bin/scripts/enrich_ip.py:

    #!/usr/bin/env python3
    import json
    import sys
    import urllib.request
    
    # Read the search results from Splunk's JSON payload
    payload = json.loads(sys.stdin.read())
    
    # Extract the IP address from the results
    for event in payload.get('results', []):
        ip = event.get('ip_address')
        if ip:
            # Send to n8n webhook
            webhook_url = "https://your-n8n-instance/webhook/enrich-ip"
            data = json.dumps({"ip": ip}).encode()
            req = urllib.request.Request(
                webhook_url,
                data=data,
                headers={"Content-Type": "application/json"},
                method="POST"
            )
            try:
                with urllib.request.urlopen(req) as response:
                    result = json.loads(response.read())
                    print(f"Enriched {ip}: {result.get('risk_level', 'unknown')}")
            except Exception as e:
                print(f"Enrichment failed for {ip}: {e}", file=sys.stderr)
    
  4. Make the script executable:
    chmod +x $SPLUNK_HOME/bin/scripts/enrich_ip.py
    
  5. Configure the alert action in Splunk UI:
    • Alert action β†’ Run a script
    • Script path: enrich_ip.py
    • Pass search results: Yes

Method B: Lookup-Driven (Splunk pulls from n8n)

This approach enriches IPs in real-time during a search rather than via alerts. You create a Python lookup that calls n8n for each IP.

  1. Create a lookup definition in $SPLUNK_HOME/etc/apps/splunk_ip_enrich/local/transforms.conf:
    [ip_enrich_lookup]
    filename = enrich_ip.py
    match_type = WILDCARD(ip)
    
  2. Create the Python lookup script in $SPLUNK_HOME/etc/apps/splunk_ip_enrich/bin/enrich_ip.py:
    #!/usr/bin/env python3
    import json
    import sys
    import urllib.request
    
    def enrich_ip(ip_address):
        """Call n8n webhook to enrich an IP address."""
        webhook_url = "https://your-n8n-instance/webhook/enrich-ip"
        try:
            data = json.dumps({"ip": ip_address}).encode()
            req = urllib.request.Request(
                webhook_url,
                data=data,
                headers={"Content-Type": "application/json"},
                method="POST"
            )
            with urllib.request.urlopen(req, timeout=5) as response:
                result = json.loads(response.read())
                return {
                    "ip_risk_level": result.get("risk_level", "unknown"),
                    "ip_summary": result.get("summary", ""),
                    "ip_country": result.get("country", ""),
                    "ip_city": result.get("city", ""),
                    "ip_isp": result.get("isp", ""),
                    "ip_threat_score": result.get("threat_score", "")
                }
        except Exception:
            # Return empty enrichment on failure
            return {
                "ip_risk_level": "unknown",
                "ip_summary": "",
                "ip_country": "",
                "ip_city": "",
                "ip_isp": "",
                "ip_threat_score": ""
            }
    
    # Splunk passes rows as JSON lines via stdin
    for line in sys.stdin:
        row = json.loads(line)
        ip = row.get("ip", "")
        if ip:
            enrichment = enrich_ip(ip)
            row.update(enrichment)
        print(json.dumps(row))
    
  3. Make it executable:
    chmod +x $SPLUNK_HOME/etc/apps/splunk_ip_enrich/bin/enrich_ip.py
    
  4. Register the lookup in $SPLUNK_HOME/etc/apps/splunk_ip_enrich/local/props.conf:
    [default]
    LOOKUP-enrich_ip = ip_enrich_lookup ip OUTPUT ip_risk_level,ip_summary,ip_country,ip_city,ip_isp,ip_threat_score
    
  5. Use it in a search:
    index=* | rex field=_raw "(?P<ip>\d+\.\d+\.\d+\.\d+)"
            | lookup ip_enrich_lookup ip OUTPUT ip_risk_level,ip_country,ip_isp
            | where ip_risk_level IN ("high", "critical")
    

Method C: Manual Enrichment from Splunk Search Results

You can enrich IPs manually from any Splunk search without setting up alerts or lookups. This is the lowest-friction way to start β€” pick IPs from your search results and send them to n8n on demand.

  1. Run your search and identify the IP addresses you want to enrich:
    index=firewall action=allow | stats count by src_ip
    
  2. Open the results table in Splunk. In the top-right corner, click Actions β†’ Export.

  3. Copy the IP you want to enrich and send it to your n8n webhook. The quickest way is a simple curl command (or use a browser):
    curl -X POST https://your-n8n-instance/webhook/enrich-ip \
      -H "Content-Type: application/json" \
      -d '{"ip": "185.220.101.42"}'
    
  4. For bulk enrichment, export results to CSV and pipe them:
    # Export your search results to CSV, then:
    while IFS=',' read -r ip rest; do
      curl -s -X POST https://your-n8n-instance/webhook/enrich-ip \
        -H "Content-Type: application/json" \
        -d "{\"ip\": \"$ip\"}"
      echo
    done < enriched_ips.csv
    
  5. Or use Splunk’s built-in β€œRun a script” action on a single event:
    • In any search results, click on an event
    • Click Actions β†’ Run a script (if configured)
    • Point to a simple enrichment script

This method requires zero Splunk configuration changes. You enrich IPs manually as you find them, which is great for ad-hoc investigations and testing your pipeline before automating it.

Method D: Alert-Driven (Automatic)

This is the automated version of Method C β€” Splunk pushes to n8n whenever a search matches, with no manual intervention needed.

Setup:

  1. Create a saved search with the query you want to monitor:
    index=* | rex field=_raw "(?P<ip_address>\d+\.\d+\.\d+\.\d+)" | stats count by ip_address
    

    (Adjust the regex to match your actual IP field name.)

  2. Set up the alert:
    • Click Save As β†’ Alert
    • Set the alert condition (e.g., β€œResults > 0”)
    • Under Alert action, choose Webhook (if the Webhook action is installed) or Custom script
  3. If Webhook action is not available (Splunk Free doesn’t include it by default), use a Custom Script action:

    Create a script at $SPLUNK_HOME/bin/scripts/enrich_ip.py:

    #!/usr/bin/env python3
    import json
    import sys
    import urllib.request
    
    # Read the search results from Splunk's JSON payload
    payload = json.loads(sys.stdin.read())
    
    # Extract the IP address from the results
    for event in payload.get('results', []):
        ip = event.get('ip_address')
        if ip:
            # Send to n8n webhook
            webhook_url = "https://your-n8n-instance/webhook/enrich-ip"
            data = json.dumps({"ip": ip}).encode()
            req = urllib.request.Request(
                webhook_url,
                data=data,
                headers={"Content-Type": "application/json"},
                method="POST"
            )
            try:
                with urllib.request.urlopen(req) as response:
                    result = json.loads(response.read())
                    print(f"Enriched {ip}: {result.get('risk_level', 'unknown')}")
            except Exception as e:
                print(f"Enrichment failed for {ip}: {e}", file=sys.stderr)
    
  4. Make the script executable:
    chmod +x $SPLUNK_HOME/bin/scripts/enrich_ip.py
    
  5. Configure the alert action in Splunk UI:
    • Alert action β†’ Run a script
    • Script path: enrich_ip.py
    • Pass search results: Yes

Which Method to Choose?

Method Best For Complexity
Method C (Manual) Ad-hoc enrichment, testing None
Method D (Alert) Monitoring specific searches Low
Method B (Lookup) Real-time enrichment in any search Medium

Start with Method C to validate your n8n pipeline works. Once you’re happy with the enrichment quality, move to Method D for automation or Method B for the most powerful inline enrichment.

Putting It All Together: The n8n Workflow

Here’s the full workflow structure:

[Webhook Trigger]
    ↓
[HTTP Request: ip-api.com]
    ↓
[HTTP Request: AbuseIPDB/VirusTotal]
    ↓
[HTTP Request: Ollama/LM Studio AI]
    ↓
[Respond to Webhook]

Each step feeds into the next. The AI node gets all the enrichment data and produces a human-readable risk assessment.

Example Output

After the pipeline runs, you get:

{
  "ip": "185.220.101.42",
  "risk_level": "high",
  "summary": "Known Tor exit node. 47 threat reports in last 90 days. Associated with scanning and brute-force activity.",
  "country": "Germany",
  "city": "Frankfurt",
  "isp": "Tor Project",
  "threat_score": 87
}

Now you know immediately: this is a Tor exit node with a high threat score. You can adjust your search or alert accordingly.

Free Tools Required

Tool Purpose Cost
n8n Workflow orchestration Free (self-hosted)
ip-api.com Geolocation Free (45 req/min)
AbuseIPDB Threat intel Free (100 req/day)
Ollama or LM Studio AI analysis Free (local)
Splunk Free Log analysis Free (200 MB/day)

Why This Matters

  • Speed: Enrichment happens in seconds, not minutes
  • Automation: No manual lookups needed
  • Free: All tools are open-source or free-tier
  • Extensible: Add more enrichment sources (Shodan, Censys, custom feeds)
  • Learning: Great way to practice n8n, APIs, and AI integration

Next Steps

  1. Set up the n8n workflow with just the geolocation step
  2. Add the threat intel lookup
  3. Add the AI analysis
  4. Connect Splunk
  5. Test with known-good and known-bad IPs
  6. Iterate β€” add more enrichment sources, refine the AI prompt

The key insight: you don’t need expensive SIEM features to get enrichment. You need a webhook, a workflow tool, and an AI model. Everything else is plumbing.


This post is part of the Cyber-AI initiative β€” free, open-source cybersecurity and AI education for everyone.