Fraud Detection with Knowledge Graphs in Neo4j: Patterns, Queries, and Production Pipelines
Why Graphs for Fraud Detection?
Financial fraud is a network problem. Money launderers move funds through shell company webs. Synthetic identity rings open accounts that transact among themselves. Insurance fraud rings stage accidents using the same participants across multiple claims. In every case, the fraud is invisible at the transaction level but becomes obvious when you examine the pattern of connections between entities.
This is why graph databases — and Neo4j in particular — have become the backbone of fraud detection platforms at Barclays, eBay, and the UK's National Crime Agency. A well-structured knowledge graph transforms fraud detection from rules-based whack-a-mole into systematic pattern-matching.
The Fraud Graph Data Model
Before writing any queries, you need a data model that captures the relevant entities and their relationships. The canonical fraud detection schema in Neo4j centres on five core node types:
| Node Label | Real-World Examples |
|---|---|
:Customer | Bank account holders, insurance policy holders |
:Account | Bank accounts, credit cards, wallet addresses |
:Transaction | Payments, transfers, claims, logins |
:Device | Phone IMEI, device fingerprint, IP address |
:Entity | Companies, shell corporations, intermediaries |
The relationships between them form the graph:
(:Customer)-[:OWNS]->(:Account)
(:Account)-[:SENT_TO]->(:Account)
(:Customer)-[:USED]->(:Device)
(:Account)-[:RECEIVED_FROM]->(:Account)
(:Entity)-[:CONTROLS]->(:Account)
(:Entity)-[:SHARES_DIRECTOR_WITH]->(:Entity)
The power of the graph model is that you can traverse these relationships in arbitrary depth — something a relational database's JOIN chains cannot scale.
Pattern 1: Circular Flow Detection (Money Laundering)
One of the strongest fraud signals is a circular money flow: funds that leave one account and return to the same ultimate beneficiary through a chain of intermediary accounts. This pattern is nearly impossible to detect in a ledger but trivial in a graph.
// Detect circular flows of depth 3-5
MATCH path = (a:Account)-[:SENT_TO*3..5]->(a)
WHERE all(r IN relationships(path) WHERE r.amount > 10000)
RETURN [n IN nodes(path) | n.accountId] AS account_chain,
reduce(s = 0, r IN relationships(path) | s + r.amount) AS total_cycled,
length(path) AS chain_depth
ORDER BY total_cycled DESC
LIMIT 20
This query finds accounts where money flows through 3 to 5 hops and returns to the origin account. The all() predicate filters for transactions above a threshold to avoid false positives from small, legitimate circular flows (e.g., refunds).
Pattern 2: Shared Device Fingerprinting (Synthetic Identity)
Synthetic identity fraud occurs when fraudsters create multiple accounts using the same device but different identities. In a graph, this manifests as star patterns where a single device node connects to many customer nodes.
// Find devices shared by unusually many customers
MATCH (d:Device)<-[:USED]-(c:Customer)
WITH d, count(c) AS customer_count
WHERE customer_count > 5
MATCH (d)<-[:USED]-(c:Customer)
OPTIONAL MATCH (c)-[:OWNS]->(acc:Account)
RETURN d.deviceFingerprint AS shared_device,
customer_count,
collect(DISTINCT c.customerId) AS suspicious_customers,
count(DISTINCT acc) AS total_accounts
ORDER BY customer_count DESC
A legitimate household might share a device among 2-3 people. When you see 10+ customers from a single device, especially if those accounts were created in quick succession, it is a strong synthetic identity signal.
Pattern 3: Community Detection for Fraud Rings
Individual patterns catch small rings. For large-scale fraud operations — the kind that involve hundreds of accounts and thousands of transactions — you need community detection. The Neo4j Graph Data Science (GDS) library provides the Leiden algorithm, which partitions the transaction graph into densely connected communities.
from neo4j import GraphDatabase
from neo4j_gds import GraphDataScience
gds = GraphDataScience("bolt://localhost:7687", auth=("neo4j", "password"))
# Project the transaction graph into memory
G = gds.graph.project(
"fraud-transactions",
"Account",
"SENT_TO",
relationshipProperties={"amount": {"type": "float"}}
)
# Run Leiden community detection
result = gds.leiden.write(
G,
writeProperty="communityId",
includeIntermediateCommunities=True,
maxLevels=10
)
print(f"Detected {result['communityCount']} communities")
print(f"Modularity score: {result['modularity']:.4f}")
# Query communities with unusually high internal transaction volume
query = """
MATCH (a:Account)
WITH a.communityId AS community, count(a) AS size
WHERE size BETWEEN 10 AND 200
MATCH path = (a1:Account)-[r:SENT_TO]->(a2:Account)
WHERE a1.communityId = community AND a2.communityId = community
WITH community, size, sum(r.amount) AS internal_volume
MATCH (a:Account)
WHERE a.communityId = community
OPTIONAL MATCH (a)-[:SENT_TO]->(other)
WHERE other.communityId <> community
WITH community, size, internal_volume, count(other) AS external_connections
WHERE external_connections < size * 0.2
RETURN community, size, internal_volume, external_connections
ORDER BY internal_volume / size DESC
LIMIT 10
"""
The key insight here is the insularity filter: fraud rings transact heavily within themselves but have few connections to the outside graph. A legitimate business sends money to suppliers and receives from customers. A fraud ring cycles money in a closed loop.
| Metric | Legitimate Community | Fraud Ring |
|---|---|---|
| Internal-to-external ratio | > 1:1 | < 5:1 (highly insular) |
| Community size | 5-50 nodes | 10-200 nodes |
| Transaction velocity | Steady | Bursts of rapid activity |
| New account ratio | < 5% | > 30% |
Pattern 4: Temporal Anomaly Detection
Fraud is not just structural — it is temporal. A burst of transactions from a dormant account, rapid fund movement through new accounts, or round-number transfers just below reporting thresholds are all graph-detectable temporal patterns.
// Detect rapid fund movement through newly created accounts
MATCH (c:Customer)-[:OWNS]->(a:Account)
WHERE c.createdAt > date() - duration("P30D")
MATCH (a)-[r:SENT_TO]->(next:Account)
WHERE r.timestamp > localdatetime() - duration("PT24H")
WITH c, a, count(r) AS outgoing_tx_24h,
sum(r.amount) AS total_outgoing_24h
WHERE outgoing_tx_24h > 10 OR total_outgoing_24h > 50000
MATCH (a)-[chain:SENT_TO*1..3]->(target:Account)
WHERE target <> a
RETURN c.customerId, a.accountId,
outgoing_tx_24h, total_outgoing_24h,
[r IN chain | r.amount] AS fund_path
ORDER BY total_outgoing_24h DESC
This query combines two signals: accounts less than 30 days old that exhibit unusually high transaction velocity (>10 outgoing transactions or >$50k moved in 24 hours), then traces where the money goes.
Production Pipeline Architecture
A production fraud detection pipeline built on Neo4j typically has three layers:
┌─────────────────────┐
│ Data Ingestion │ Kafka → Neo4j CDC → batch loaders
├─────────────────────┤
│ Graph Analytics │ GDS projected graphs → community detection
├─────────────────────┤
│ Alert Generation │ Cypher alerts → case management system
└─────────────────────┘
Data ingestion is the hardest part. Transaction data arrives as flat records; you must transform each record into a graph pattern (customer → account → transaction → counterparty). Tools like Apache Kafka with the Neo4j Connector or a custom CDC pipeline handle this at scale.
Graph analytics runs on a scheduled basis (hourly or daily). The GDS library projects a subgraph into memory, runs community detection, centrality, and similarity algorithms, and writes results back as node properties.
Alert generation queries the enriched graph for specific patterns. Each pattern generates a risk score. Accounts that exceed a configurable threshold are routed to a case management system for manual review.
Choosing the Right Approach
| Fraud Type | Best Detection Pattern | Algorithm | Latency |
|---|---|---|---|
| Money laundering | Circular flow | Cycle detection (Cypher) | Real-time |
| Synthetic identity | Shared device / phone | Star pattern (Cypher) | Near real-time |
| Fraud rings | Insular communities | Leiden (GDS) | Batch (hourly) |
| Account takeover | Sudden velocity change | Temporal anomaly | Real-time |
| Bust-out fraud | Rapid credit utilisation | Similarity + sequence | Batch (daily) |
Limitations and Pitfalls
Graph-based fraud detection is powerful but not magic. Common failure modes include:
-
False positives from legitimate networks: A family of five sharing one computer and one bank account looks identical to a synthetic identity ring. You must layer in non-graph signals — identity verification scores, credit bureau data — to disambiguate.
-
Scaling to billion-node graphs: Neo4j handles tens of millions of nodes well, but billion-scale transaction graphs push against hardware limits. GDS projections must be carefully scoped (time-windowed subgraphs, sampled relationships) to stay performant.
-
Adversarial graph evasion: Sophisticated fraud rings actively probe detection rules. If you look for cycles of length 3-5, they route through 6 hops. If you flag accounts sharing a device, they use residential proxies. Your detection queries must evolve continuously, and your graph model should capture enough entity types to make evasion costly.
Building on What You Already Know
This article builds on several topics covered elsewhere in the collection:
- Graph Data Modeling Patterns — The reification and intermediate node patterns from that article are essential for modelling transactions and their metadata.
- Graph Algorithms with Neo4j — Shortest path and centrality algorithms complement the fraud detection toolkit.
- Community Detection Algorithms — A deeper dive into the Leiden algorithm used in the GDS example above.
Fraud detection remains one of the highest-ROI applications of graph technology. The patterns here are production-tested at institutional scale. Start with a focused data model, add one detection pattern at a time, and measure your false positive rate relentlessly — the graph will surface signals you never knew existed.