Detecting financial fraud with Neo4j

Building geolocation-enriched graphs using IP metadata.

Online insurance applications generate a trail of events—submissions, underwriting decisions, payments—each tagged with an IP address. By enriching these events with Fastah IP geolocation and loading them into Neo4j, you can detect fraud patterns that tabular data would hide.

This guide extends Neo4j's fraud detection documentation by adding privacy-safe geographic attributes to application events. When submission and payment originate from distant locations—or cluster with known bad actors—you can trigger a human review.

Neo4j fraud ring illustration

Graph relationships reveal fraud rings that tabular queries miss. Source: Neo4j.

The fraud pattern

Each insurance application generates events with IP addresses:

  • applicationSubmitted — applicant submits from IP address A
  • paymentMade — payment arrives from IP address B

If the distance between A and B exceeds a threshold (e.g., 500 km), flag for review.

Event schema:

FieldDescription
customerApplicationIdUnique application identifier (e.g., INSURE-ME-1708)
eventtypeEither applicationSubmitted or paymentMade
timestampUnix epoch milliseconds
ipPublic IPv4 address
locationData.latLatitude from Fastah API
locationData.lngLongitude from Fastah API

Load geolocated events into Neo4j

This Cypher query ingests Fastah API responses, creates IPAddress nodes with spatial points, and links them to Application nodes:

// Ingest events and create graph structure
UNWIND [
  {
    ip: "15.220.176.1",
    timestamp: 1705124935277,
    eventtype: "paymentMade",
    customerApplicationId: "INSURE-ME-1708",
    locationData: { lat: 39.04, lng: -77.49 }
  },
  {
    ip: "13.107.54.1",
    timestamp: 1706199630411,
    eventtype: "applicationSubmitted",
    customerApplicationId: "INSURE-ME-1708",
    locationData: { lat: 47.61, lng: -122.33 }
  }
] AS event

// Create IP node with Neo4j spatial point (SRID 4326)
MERGE (ip:IPAddress { ip: event.ip })
SET ip.location = point({
  latitude: event.locationData.lat,
  longitude: event.locationData.lng
})

// Create application node
MERGE (app:Application { id: event.customerApplicationId })

// Link based on event type
FOREACH (_ IN CASE WHEN event.eventtype = "applicationSubmitted" THEN [1] ELSE [] END |
  MERGE (app)-[:SUBMITTED_FROM { timestamp: event.timestamp }]->(ip)
)
FOREACH (_ IN CASE WHEN event.eventtype = "paymentMade" THEN [1] ELSE [] END |
  MERGE (app)-[:PAID_FROM { timestamp: event.timestamp }]->(ip)
)

In production, stream events from your application or ETL pipeline rather than hard-coding them.

Detect suspicious applications

Find applications where submission and payment IPs sit far apart:

// Find applications with >500 km between submission and payment locations
MATCH (app:Application)-[:SUBMITTED_FROM]->(submitIp:IPAddress),
      (app)-[:PAID_FROM]->(payIp:IPAddress)
WITH app,
     submitIp,
     payIp,
     point.distance(submitIp.location, payIp.location) / 1000.0 AS distanceKm
WHERE distanceKm > 500
RETURN app.id AS applicationId,
       round(distanceKm) AS distanceKm,
       submitIp.ip AS submittedFromIp,
       payIp.ip AS paidFromIp
ORDER BY distanceKm DESC

The point.distance() function returns meters, so we divide by 1000 for kilometers.

Visualize with Neo4j Bloom

Use Neo4j Bloom to explore relationships between applications and shared IP addresses:

Multiple applications sharing the same IP address and/or a geographical cluster may trigger a review

Neo4j's graph structure reveals patterns you'd miss in tabular data—such as fraud rings where multiple applications share IP addresses, or account takeovers where submission and payment originate from different continents.


What’s Next