Skip to content

Detecting Fraud Rings With Graph Analysis

10 min read · updated August 11, 2026

A fraud ring is a set of accounts operated by one party that are built to look independent. Individually each one is unremarkable, which is precisely why per-account models miss them. The connection is in the attributes they could not help sharing, and it only becomes visible when you make those attributes nodes.

Why per-account scoring misses a ring

A per-account model scores rows. Everything it can say about account A2 is a function of A2’s own columns: signup date, funding method, transaction pattern, device type. A competent ring operator makes each of those look ordinary, and the model is doing exactly what it was asked when it passes all six accounts.

What the operator cannot easily vary is the substrate. Registrations come from a device pool, funding from a card pool, verification from a phone-number pool, and reuse across accounts is the cheapest thing about the operation. Those reuses are not columns on a row — they are edges between rows, and they only exist as data once you build a graph.

The bipartite graph and its projection

Start with two kinds of node: accounts, and the identifiers they touch. Edges only go between the two kinds, so the graph is bipartite by construction. Six accounts and three identifiers:

accounts:    A1 A2 A3 A4 A5 A6
identifiers: D1 (device fingerprint)
             IP1 (address)
             P1 (phone)

edges (account — identifier)
  A1-D1   A2-D1   A3-D1
  A2-IP1  A3-IP1  A4-IP1
  A5-P1   A6-P1

Now project onto the accounts: join two accounts whenever they share at least one identifier. D1 is shared by {A1, A2, A3}, which contributes all three pairs among them. IP1 is shared by {A2, A3, A4}, contributing its three pairs — one of which, A2–A3, already exists. P1 gives A5–A6.

projected account graph
  A1-A2  A1-A3  A2-A3        (from D1)
  A2-A3  A2-A4  A3-A4        (from IP1, A2-A3 already present)
  A5-A6                      (from P1)

distinct edges: A1-A2, A1-A3, A2-A3, A2-A4, A3-A4, A5-A6  →  6 edges

Note the mechanical consequence of projection: an identifier shared by k accounts creates k(k-1)/2 edges. A device seen on 3 accounts creates 3 edges; on 30 accounts it creates 435. Projection amplifies, which is useful for finding rings and ruinous if you do it without the weighting in the next section but one.

Scoring the subgraph, worked

The candidate is the connected component {A1, A2, A3, A4}. Density of an undirected graph is the fraction of possible edges that exist, 2m / (n(n-1)):

candidate component {A1, A2, A3, A4}
  n = 4, possible edges = 4·3/2 = 6
  present: A1-A2, A1-A3, A2-A3, A2-A4, A3-A4  →  m = 5
  density = 2·5 / (4·3) = 10/12 = 0.833

whole projected graph
  n = 6, possible edges = 15
  m = 6
  density = 2·6 / (6·5) = 12/30 = 0.400

component {A5, A6}
  n = 2, m = 1, density = 1.000

Two things fall out of that arithmetic immediately. The first is the real signal: the four-account component is more than twice as dense as the graph it sits in, and 5 of 6 possible connections among four supposedly unrelated strangers is not something that happens by accident. The second is a trap you have to handle in code: the pair {A5, A6} has density 1.0 and means nothing at all, because density is degenerate for tiny n. Any density score needs a minimum size — and the more robust statistic for this job is the average degree of the induced subgraph (here 2m/n = 2.5), which does not saturate. See graph density for the general treatment.

At real scale you do not enumerate components and eyeball them. You run a community-detection algorithm and then score each community by the same induced density. The usual starting point is the Louvain method (Blondel et al., 2008), which greedily maximises modularity; its known defect is that it can return communities that are internally disconnected, which for this use case means a “ring” that is two unrelated groups. The Leiden algorithm (Traag, Waltman and van Eck, 2019) was introduced specifically to guarantee connected communities and is the better default now. Both take a resolution parameter that sets how large the communities come out; it is not a nuisance knob, it is the single most consequential setting in the pipeline and it has to be tuned against labelled cases.

The coffee-shop problem

Treat every shared identifier as equal evidence and the first thing your system finds is an airport. A carrier-grade NAT address can front tens of thousands of subscribers. A university IP, a corporate proxy, a popular VPN exit node, a stock device fingerprint from a common phone model with default settings — all of these produce enormous, entirely innocent cliques.

The fix is the same idea that makes Adamic-Adar work in link prediction: weight each shared identifier by the inverse of how many accounts touch it.

weight of a shared identifier z:  w(z) = 1 / log |accounts(z)|

  device seen on 3 accounts      w = 1/ln 3    = 0.910
  IP seen on 40 accounts         w = 1/ln 40   = 0.271
  IP seen on 4,000 accounts      w = 1/ln 4000 = 0.121

edge weight between two accounts = sum of w(z) over shared z

With those weights the A2–A3 edge (sharing both D1 and IP1) is worth 0.910 + 0.910 = 1.820, while an edge created purely by a 4,000-account IP is worth 0.121 and falls below any sensible threshold. Many systems go further and hard-cap: an identifier touching more than some number of accounts is excluded from edge creation altogether, because past a certain fan-out it is infrastructure rather than identity.

Deciding what counts as one identifier is a larger source of error than the algorithm. Are two devices with the same fingerprint the same device? Are two spellings of an address the same address? Entity resolution decisions here change the graph more than any parameter you will tune afterwards, and they should be reviewed as carefully as the model.

What the graph does not decide

A dense component is a reason to look, not a finding. Shared attributes have innocent explanations that the topology cannot distinguish from guilty ones: a family sharing an address and a tablet, a small business whose staff sign up from one office, a reseller legitimately operating several accounts, two people who bought the same phone and never changed a setting.

  • Route to review, not to action. The output of this pipeline is a ranked queue for human investigators. In most jurisdictions, consequential decisions about a customer’s account carry notification, explanation and appeal obligations, and a graph score is not an explanation. Treat the legal requirements as a design input and take advice on which apply to you.
  • Labels arrive late and incomplete. Confirmed fraud is known weeks or months after the fact, and rings you never caught are recorded as legitimate. Any supervised model trained on those labels is learning what your current controls already catch.
  • The adversary reads your rules. Once identifier reuse is penalised, operators stop reusing identifiers — fresh devices, residential proxies, one number per account. Graph signals decay under pressure in a way a static model does not, which is why the durable version of this looks at behavioural co-movement (timing, amounts, sequence) as well as shared attributes. That layering is the subject of bot network detection.