Fraud Detection

The Float Service runs a series of fraud checks during float creation and uses an embedded ML model to inform ACH routing decisions during collections. Both are controlled by configuration and GrowthBook feature flags, allowing individual checks to be toggled or bypassed without a code deployment.

Float Creation Fraud Checks

Fraud checks run as part of POST /{user_id}/floats after subscription validation and before underwriting. If any enabled check fails, the float is rejected. The results are always saved back to the Payments Service after the attempt, regardless of whether the float was approved or denied.

Check Sequence

Float creation request received
        │
        ▼
1. Check App Version
   Minimum build version met? ──No──► ErrAppVersionInvalid ──► reject float
        │ Yes
        ▼
2. Check Float Amount
   Amount within allowed maximum? ──No──► ErrFloatAmount ──► reject float
        │ Yes
        ▼
3. Check Recent Floats (queries RDS)
   Float taken in last 24 hours? ──Yes──► ErrRecentFloat ──► reject float
   Outstanding float exists? ──────Yes──► ErrOutstandingFloat ──► reject float
        │ No
        ▼
4. Check Active Installment Loans (LOC Service)
   OPEN or DEFAULTED loan exists? ──Yes──► ErrOutstandingLoan ──► reject float
        │ No
        ▼
5. Payments Service Fraud Check (external)
   Check fails? ──Yes──────────────────────► reject float
   Connectivity error? ──Yes──────────────► ErrConnectivity ──► reject float
        │ Pass
        ▼
Float creation proceeds (underwriting, disbursement, etc.)
        │
        ▼
Save fraud check results to Payments Service
  (skipped if user is bypassed or Payments check had a connectivity error)

Each check is independently enabled. A check that is disabled is skipped and treated as passed.

Checks Reference

Check What it validates External call

App Version

The requesting app’s build version meets a configured minimum.

None — validated against a configured value.

Float Amount

The requested amount does not exceed the configured maximum.

None — validated against a configured value.

Recent Floats

No float was taken in the last 24 hours and no outstanding float exists for the user.

RDS — queries the floats table directly.

Active Installment Loans

No OPEN or DEFAULTED installment loans exist for the user.

LOC Service — ListLoansForUser()

Payments Service

External fraud check using user context: account ID, float type, install ID, user ID, zip code.

Payments Service — FloatFraudCheck()

Bypass

Users with an active requirements bypass record (see Bypass & Payback) have the Bypassed flag set to true when fraud checks are invoked. In bypass mode, the underwriting eligibility check is skipped entirely. Individual fraud checks may still run (depending on configuration), but the Bypassed flag is passed to the Payments Service fraud check, which may apply different rules for bypassed users.

Fraud check results are not saved back to the Payments Service for bypassed users.

ACH Routing ML Model

During initial collection attempts, the collections engine can use an embedded logistic regression model to decide whether to route a collection to ACH or pinless debit. This supplements the debit card validity check with a probability-based prediction of ACH success.

Model Details

Property Value

Model name

adv_ach_first_attempt_lr_v2

Algorithm

Logistic regression

Decision threshold

0.50 — route to ACH if predicted probability ≥ 0.50

Activation

GrowthBook flag floats.collections.local_model must be enabled for the user

Storage

Embedded in the Lambda binary as pkg/collections/model.json

Input Features

Feature Type Description

FLOAT_RANK

Numeric

The user’s float sequence number (1st float, 2nd float, etc.)

ADVANCE_AMOUNT

Numeric

Dollar amount of the float

USER_TENURE_DAYS

Numeric

Days since the user joined the platform

DAYS_SINCE_ADVANCE_FUNDED

Numeric

Days elapsed since the user’s previous float was funded

SUB_ACH_SUCCESS_EVER_FLAG

Binary (0/1)

Whether the user has ever had a successful ACH collection

BALANCE_AT_ATTEMPT

Numeric

User’s bank account balance at the time of the collection attempt

SUBMIT_DAYOFWEEK

Numeric (0–6)

Day of the week the attempt is made (0 = Monday)

SUBMIT_HOUR

Numeric (0–23)

Hour of the day the attempt is made

inst_[ID] / inst_OTHER / inst_UNKNOWN

Binary (0/1)

One-hot encoded institution indicator — over 100 top institutions with fallback categories

Audit Trail

When the model is used, the collection attempt’s DynamoDB log entry includes:

  • ach_model_version — the model name used

  • ach_model_features — the full feature map fed to the model

  • ach_model_probability — the raw probability score output

  • ach_model_override — whether the model decision was manually overridden

See DynamoDB Tables for the full collection-history schema.

GrowthBook Feature Flags

Flag Effect

floats.collections.local_model

Enables the embedded ACH routing ML model for a user’s initial collection attempt. When disabled, the collections engine uses the standard debit card validity check alone.

floats.collections.max_ach_attempts

Maximum number of automated ACH attempts before a float is defaulted. Default: 3.

floats.collections.max_attempts_on_day

Maximum total collection attempts per day for the webhook balance worker. Default: 3.