Float Lifecycle

A float is a short-term advance disbursed to a user. Every float progresses through a set of statuses from the moment it is created until it is either fully collected or written off. This page covers the end-to-end lifecycle: creation, collection, manual payback, and terminal states.

Float Statuses

Status Transitions

SCHEDULING  ──[T-1 Day: no valid debit card, ACH submitted]─────► ACHSENT
SCHEDULING  ──[Due Date: pinless success]───────────────────────► COMPLETED
SCHEDULING  ──[Due Date: all attempts fail]─────────────────────► RETRY
SCHEDULING  ──[Manual payback]──────────────────────────────────► COMPLETED

ACHSENT     ──[ACH settles: FLOAT_DEBIT_COMPLETED event]────────► COMPLETED
ACHSENT     ──[ACH returned: FLOAT_DEBIT_RETURNED event]────────► RETRY
ACHSENT     ──[Chargeback: FLOAT_CREDIT_RETURNED event]─────────► DEFAULTED

RETRY       ──[Daily Retry or webhook: pinless/ACH success]─────► COMPLETED
RETRY       ──[Daily Retry or webhook: ACH submitted]───────────► ACHSENT
RETRY       ──[ACH attempt limit reached]───────────────────────► DEFAULTED
RETRY       ──[> 90 days past due]──────────────────────────────► DEFAULTED
RETRY       ──[No valid Plaid account and no valid debit card]──► UNCOLLECTABLE

UNCOLLECTABLE ──[Daily Retry re-evaluation, payment method found]► RETRY

COMPLETED   (terminal)
DEFAULTED   (terminal)

Status Reference

Status Description

SCHEDULING

Initial status set at float creation. The disbursement has been submitted and the float is awaiting its due date.

ACHSENT

An ACH debit has been submitted to the payment processor. The float remains in this state until the ACH Handler Lambda receives a settlement callback from the prod-payments Kinesis stream.

COMPLETED

The float has been fully collected. Set by the ACH Handler on a successful ACH settlement, or directly by the collections engine on a successful pinless debit or manual payback.

RETRY

A collection attempt was made but failed. The float will be retried by the Daily Retry scheduler or by the webhook workers on the next qualifying income or balance event.

DEFAULTED

The float has exceeded the maximum ACH attempt count, is more than 90 days past due, or was charged back. Terminal.

UNCOLLECTABLE

No valid Plaid account and no valid primary debit card exist for the user. Re-evaluated on each subsequent Daily Retry run — not a permanent terminal state.

Disbursement

When a float is created via POST /{user_id}/floats, the API Lambda runs a series of validation and eligibility checks before submitting the disbursement. All steps are synchronous; the float record is not written to RDS until disbursement succeeds.

POST /{user_id}/floats
        │
        ▼
Get user profile (User Service)
        │
        ▼
User state is not restricted? ──No──► 400
User status is ACTIVE? ──No──────────► 400
Email verified (if enforced)? ──No──► 400
        │ Yes
        ▼
Get subscription status (Subscription Service)
  PAST_DUE or STALE? ──Yes──► 400
        │ No
        ▼
Acquire distributed lock (DynamoDB)
        │
        ▼
Get linked bank account (Transactions Service)
        │
        ▼
Check requirements bypass (DynamoDB)
        │ (bypass skips underwriting eligibility check below)
        ▼
Run fraud check
  Connectivity error? ──Yes──► 500
        │ No
        ▼
Get float profile and fee (Underwriting Service)
  Float not enabled for user? ──Yes──► 400
        │ No
        ▼
Get next payday and payback date (Insight Service)
  Custom payback date requested? ──validate within allowed window──►
        │
        ▼
Check underwriting eligibility (Underwriting Service)
  Not approved and no bypass? ──Yes──► 400
        │ Approved or bypassed
        ▼
Submit disbursement (Payments Service)
  Type routes to: PINLESS credit, RTP credit, or ACH credit
  RTP error + fallback enabled? ──Yes──► retry as PINLESS
        │ Success: returns confirmationID and resolved loan type
        ▼
Write float record to RDS
  DebitStatus: SCHEDULING
  CreditID: confirmationID from Payments Service
  DebitDate: computed payback date
        │
        ▼
Send confirmation email (Segment + Iterable with retry)
  PINLESS/RTP ──► instant transfer template
  NORMAL ──────► standard ACH template
        │
        ▼
Publish user_float_created event (EventBridge)
        │
        ▼
Return 201 Created
If the disbursement succeeds but the RDS write fails, the float record is lost while funds have already moved. The fraud check result and confirmation email are sent only after the RDS write succeeds.

Collection

After a float is created, the collections engine takes over. It operates through three scheduled stages and two webhook-triggered paths.

See Collections Engine for detailed flow diagrams covering T-1 Day, Due Date, Daily Retry, and webhook-triggered collection.

ACH settlement callbacks are processed by the ACH Handler Lambda. See ACH Processing for how settlement events update float status.

Manual Payback

A user can pay back an active float before the due date via POST /{user_id}/floats/{float_id}/payback. The request is routed directly to the Payments Service; the Float Service does not directly update the float status.

POST /{user_id}/floats/{float_id}/payback
        │
        ▼
Submit payback (Payments Service)
        │
        ├── ErrFloatNotPayable ──► 404
        ├── Other error ──────────► 500
        │
        ▼
Check resulting debit status:
        │
        ├── ACHSENT or COMPLETED ──► 202 Accepted
        └── Other ─────────────────► 500

The float’s final COMPLETED status is set asynchronously when the ACH Handler processes the corresponding settlement event from the prod-payments Kinesis stream.

Payback for Account Reactivation

When a user reactivates a cancelled account, all past-due floats must be paid off before reactivation can proceed. This is handled by POST /{user_id}/floats/payback/reactivate.

POST /{user_id}/floats/payback/reactivate
        │
        ▼
Get all active floats for user
  Active statuses: RETRY, DEFAULTED, SCHEDULING, ACHSENT, UNCOLLECTABLE
        │
        ▼
Filter to past-due floats (DebitDate <= now)
  Calculate total responsibility = sum(amount + fee) across all past-due floats
        │
        ▼
For each past-due float:
        │
        ├── Submit reactivation payback (Payments Service)
        │       │
        │       ├── ErrFloatNotPayable ──► log warning, subtract from
        │       │                         total responsibility, continue
        │       ├── Other error ──────────► 500
        │       │
        │       └── Success
        │               │
        │               ├── DebitStatus != COMPLETED ──► 402 Payment Required
        │               │   {AmountCollected, AmountRemaining, MaskedCardNumber}
        │               │
        │               └── COMPLETED ──► add to collected, continue to next float
        │
        ▼
Return 200 OK
  {AmountCollected, AmountRemaining, AmountTotal, MaskedCardNumber}
A 402 response indicates partial collection — some floats were paid but at least one could not be collected. The response body includes amounts for both collected and remaining, which the caller uses to determine whether reactivation can proceed.

Default & Uncollectable

DEFAULTED is set under three conditions:

  • ACH attempt limit exceeded — The number of automated ACH attempts reaches the configured limit (checked by Daily Retry and webhook workers).

  • Age threshold — The float is more than 90 days past its due date (checked by Daily Retry).

  • Chargeback — The Payments Service reports a FLOAT_CREDIT_RETURNED event on the prod-payments Kinesis stream. The ACH Handler sets the float to DEFAULTED and triggers a user ban via the User Service.

UNCOLLECTABLE is set by the Daily Retry scheduler when neither a valid Plaid account (to check balance) nor a valid primary debit card exists. Unlike DEFAULTED, it is not terminal: the Daily Retry scheduler re-queues UNCOLLECTABLE floats on each run, so if the user later adds a valid payment method, collection will be reattempted automatically.