Fraud ring detection beyond the obvious signals
A textbook fraud ring on a Shopify store looks like five or six customer accounts, all created within a week of each other, all shipping to a small cluster of addresses, all running the same return pattern. They do not buy the same SKU. They do not all use the same email domain. They do not coordinate timing in obvious ways. The whole point of the ring is to look like five or six unrelated customers.
That is what makes them hard to detect. Each individual customer in the ring looks plausible. Their order frequency is moderate, their return rate is high but not absurd, their account is freshly created but so are most new customers, their addresses are different. None of the per-customer signals fire above the medium-zone threshold.
This post is about the second category of signals that catch rings. Not customer-keyed, but pivot-keyed. Addresses, email hashes, phone hashes, and the temporal clustering that ring activity exhibits. Spec 147 ships several of these. Combined with the existing fraud-ring detection (spec 030), they form a layered system that catches rings the per-customer signal set cannot.
The two-layer model
RefundSentry has had a fraud-ring detection pipeline since spec 030. That system runs as a periodic background job. It looks for clusters of customer accounts at shared addresses with elevated return rates, and creates a FraudRingAlert record when the cluster crosses a confidence threshold.
The fraud-ring system catches well-formed rings: clusters of three or more customers at a single address with return rates several standard deviations above the shop median. It does not catch:
- Rings with two members (below the cluster threshold)
- Rings spread across two or three addresses (no single-address cluster meets the threshold)
- Rings caught early in their activity, before enough returns have accumulated for the statistical clustering to register
- Individual returns from members of a confirmed ring (the ring alert is on the cluster, the score on the return does not automatically inherit it)
Spec 147's identity-pivot signals fill these gaps from the score side.
How the new signals interact with the existing ring detection
The two systems are complementary, with deliberately different latencies and different data sources.
The fraud-ring system runs as a background job. It writes FraudRingAlert rows when it detects a cluster. Merchants see these alerts on the Fraud Rings dashboard. Investigation is human-driven from there.
The identity-pivot signals run inline on every return at score time. They read the AddressFingerprint table, the ChargebackEvent table, the CustomerProfile table, and the FraudRingAlert table. The signals return their evaluation on each return, contributing points to the score in real time.
The shared data structure connecting them is the customerIds array on AddressFingerprint, which lists every Shopify customer GID that has ever shipped to that fingerprint, plus the customerIds array on FraudRingAlert, which lists the members of each detected ring.
The new sharedWithFraudConfirmed signal joins these two arrays. It fires when the current return's address fingerprint has at least one customerIds entry that also appears in a FraudRingAlert(status="CONFIRMED"). In SQL terms, an array intersection. In effect: any new customer shipping to an address that has been part of a confirmed ring inherits an immediate score elevation, even if they are not themselves a member of the ring.
This propagation is what was missing before. Confirmed fraud-ring labels were trapped on the ring's customer list. Now they propagate through the address pivot to any new customer at the same address.
The recent-velocity angle
Catching rings early matters. A ring that has been running for six months has cost your store six months of refund payouts. A ring caught after its second chargeback has cost two.
The recentChargebackVelocityAtAddress signal targets the early-detection window. It fires on 2 or more chargebacks at the same address within the trailing 90 days, regardless of lifetime count.
The reason this matters is that the lifetime-count signal (priorChargebackAtAddress) only crosses its strong-tier threshold at 3+ lifetime chargebacks. A ring that has produced 2 chargebacks total at the address would land in the 1.5x tier of the lifetime signal but fire fully on the velocity signal. The combined contribution is a more aggressive score elevation on early-stage rings than either signal alone.
The 90-day window is the merchant-tunable default. Stores with longer chargeback dispute cycles can stretch the window to 180 days. Stores with shorter cycles can tighten it to 30 or 45. Tuning happens in Risk Settings, same as every other threshold.
For more on how the velocity signal relates to the broader address-pivot story, see how fraudsters reuse addresses after a chargeback.
What rings actually look like in the wild
A few patterns from real merchant investigations:
The two-address ring. Three customer accounts, two shipping addresses, a clear pattern of one address used for the high-value items and the other used for the low-value items. The distribution defeats single-address clustering. Each address has fewer than 3 customers, so neither crosses the multi-account threshold. The fraud-ring system stays silent.
What catches it: the velocity signal at the higher-volume address fires on the second chargeback. The confirmed-fraud propagation (sharedWithFraudConfirmed) extends from one address to the other once any customer at either address gets confirm-fraud labeled by the merchant.
The cross-platform ring. Members coordinate across multiple Shopify stores, with each store seeing only one or two chargebacks per ring member. Within any single store, the ring is invisible. Across stores, the pattern is obvious to a human looking at the data, but the data lives in different merchants' Shopify accounts.
What catches it: nothing in spec 147 directly. The per-shop scoping rule prevents cross-merchant data sharing for privacy reasons. A future cross-shop fraud-ring product could address this with explicit merchant opt-in. For now, the cross-platform ring is a gap that the per-shop signal set cannot close.
The household ring. Two or three legitimate customers at one household, plus one or two fraudulent accounts at the same address used by a different person living there. Distinguishing legitimate from fraudulent within the cluster is difficult. The cluster fires the address-cluster signal, but the merchant has to decide which members are real.
What catches it: the merchant-confirmation workflow. Once a merchant labels one of the legitimate customers as confirm-not-fraud and one of the fraudulent as confirm-fraud, the engine learns. The signals do not have to make the legitimate-vs-fraudulent call automatically; they elevate the score and let the merchant adjudicate.
The role of confidence
Adding identity-pivot signals raises the per-score NOT_AVAILABLE count on shops that do not yet have backfilled chargeback data. Spec 147 raises the engine's confidence ceilings to absorb this: HIGH confidence is now ≤12 NOT_AVAILABLE entries (was ≤9), and MEDIUM is now ≤22 (was ≤18). The new ceilings are validated against the parity fixture extension and adjusted by ±1 if the fixture data demands.
In practical terms: a fresh-install shop scoring its first return after the spec 147 release will see 5-7 of the new signals report NOT_AVAILABLE, and confidence will resolve to MEDIUM. As the shop accumulates chargebacks and the backfill completes, NOT_AVAILABLE counts drop and HIGH-confidence scores become routine.
The confidence tier matters because some merchant workflows are gated on it. A "hold for review" automation might require HIGH-confidence high-zone scores before triggering. A "block return entirely" automation might require both HIGH confidence AND a score above 80. The widened confidence ceilings preserve those workflows during the catch-up window.
What this changes operationally
Three concrete shifts for merchants who turn this on:
The first is that ring-related returns now elevate without needing the fraud-ring system to first detect the cluster. The signals run inline on every return. Even before the periodic fraud-ring job has produced a FraudRingAlert, individual returns from ring members will start scoring up because of the address-keyed signals.
The second is that the 30-day window between the fraud-ring system detecting a cluster and the merchant labeling its members is shorter. As soon as one member is confirm-fraud labeled, the propagation signals (priorFraudAtAddress, sharedWithFraudConfirmed) extend to other members and to new customers at the same addresses. The labeling effort compounds.
The third is that the velocity signal lets you catch rings two or three chargebacks earlier than you could before. For a store doing a few chargebacks a month, this can be the difference between catching a ring at $400 in losses and catching it at $1,200.
For more on how the email and phone pivots add another layer of cross-customer ring detection, see email and phone recycling in return fraud.
Common questions
How is this different from the existing fraud-ring detection (spec 030)?
The existing system detects clusters and creates FraudRingAlert rows for human review. The new identity-pivot signals score individual returns inline at score time. They use overlapping data (the AddressFingerprint table, the customerIds arrays) but operate at a different layer. The two systems are complementary, not redundant.
Can the velocity signal produce false positives on stores with high legitimate return rates?
The signal looks for chargebacks at the address, not returns. Even a store with a 30% return rate has a chargeback rate measured in basis points. Two chargebacks at the same address in 90 days is a real signal regardless of the store's return rate. Merchants can tune the threshold up if their specific fraud profile demands.
Does the propagation signal fire on every customer at a previously fraud-confirmed address?
Yes. That is the design. Any new customer shipping to an address with a CONFIRMED FraudRingAlert member or a CONFIRMED_FRAUD outcome inherits the elevated score. Merchants can confirm-not-fraud on any specific return to record a positive label and reduce future contributions of the signal at that fingerprint.
What if a CONFIRMED FraudRingAlert turns out to be a false positive?
The merchant can dismiss the alert via the Fraud Rings dashboard. Once dismissed, the alert no longer counts as CONFIRMED, and the propagation signal stops firing on it. The signal evaluates the current state of the alert table at score time, so dismissals propagate immediately on the next scored return.
Will adding these signals make my engine slower?
The new queries add roughly 2 ms per return on a typical shop. The composite indexes on [shopId, addressFingerprintId] and the array-intersection queries on customerIds (which use a Postgres GIN index) keep the lookups fast even at scale.
Closing thought
A fraud ring that operates well is, by design, invisible to per-customer signals. The team running the ring spends real effort making each member look like a normal customer. The cross-customer pivots, addresses, emails, phones, are the data they cannot afford to randomize all at once. That asymmetry is what makes pivot-keyed detection work.
RefundSentry is free during the private beta. The pivot signals run alongside the existing fraud-ring detection and the per-customer signal set, all inside the unified risk engine. See the pricing page for plan details and the docs for the full signal reference.
For a real-world walkthrough of how these signals catch a coordinated multi-customer fraud operation, the next post in this series is the anatomy of a repeat return fraudster.