User Lifecycle

Overview

A user progresses through a defined set of statuses from account creation through cancellation. The status is stored in both DynamoDB (user_status) and RDS (employment_validation) and controls what actions the user can take and whether they are billed.

User Status

user status
Status Trigger Description

PROCESSING

Account creation

Default status set when the Auth0 account is first created. User cannot take out floats and is not billed.

ACTIVE

Successful activation

User has linked a bank account, has an active primary debit card, and has an activated subscription. User is eligible for floats and billing.

PAUSED

Cancellation or MX action

Subscription has ended. User can log in but is shown a reactivation screen. Not billed.

INVESTIGATE

MX action

User is flagged for review. Cannot take out floats. Not billed. MX can clear this status.

BANNED

MX action or chargeback

User is blocked from logging in. Triggered automatically when a CHARGED_BACK payment event is received. MX can unban, but user must still reactivate.

Signup Flow

New users go through a two-step process: account creation followed by activation. Activation may happen immediately (Plaid Layer flow) or later when the user connects their bank account.

Each successful user creation emits the Datadog distribution user.signup (value 1), tagged with:

  • platform:<app_platform>

This is the entry point of the signup funnel and is intentionally separate from the user.membership.* namespace, which tracks membership state transitions rather than initial account creation.

Standard Flow

POST /users
     │
     ▼
Validate phone number (deduplicate)
Validate access token
     │
     ▼
Save user record (status: PROCESSING)
     │
     ├── Require MFA in Auth0
     ├── Add START_DATE tag
     ├── Accept SMS terms (if requested)
     └── Save phone number
     │
     ▼
Return 201 Created
(user activates later via POST /{user_id}/user/activate)

Plaid Layer Flow

When a layer_token is provided at signup, Plaid account linking happens inline and activation is attempted immediately after user creation.

POST /users (with layer_token)
     │
     ▼
Create Plaid items from Layer stub if reactivating (Transactions service)
     │
     ▼
Save user record (status: PROCESSING)
     │
     ▼
Attempt activation (see below)
     │
     ▼
Return 201 Created

Activation

Activation transitions a user from PROCESSING to ACTIVE. Two paths exist depending on which onboarding flow the user went through.

Each successful activation (an actual PROCESSING → ACTIVE transition) emits the Datadog distribution user.activated (value 1), tagged with:

  • flow:legacy (POST /{user_id}/user/activate, including the inline Plaid Layer path) or flow:prequalify (POST /{user_id}/user/activate/prequalify)

  • platform:<app_platform>

The metric is only emitted on a real transition — no-op calls for users that are already ACTIVE (or not in PROCESSING) do not emit it. Together with user.signup, this forms the signup funnel: user.signupuser.activated.

Legacy Activation (POST /{user_id}/user/activate)

Triggered explicitly or inline during the Plaid Layer signup flow. Validates that the user has connected a bank account and debit card before proceeding.

User status must be PROCESSING
     │
     ▼
Has active Plaid items? ──No──► Return (user stays PROCESSING)
     │ Yes
     ▼
Has a main account? ──No──► Return (user stays PROCESSING)
     │ Yes
     ▼
Has an active debit card? ──No──► Return (user stays PROCESSING)
     │ Yes
     ▼
Has a primary debit card? ──No──► Return (user stays PROCESSING)
     │ Yes
     ▼
Activate subscription (Subscription Service)
     │
     ▼
Create membership record (base tier, monthly term)
     │
     ▼
Update user status → ACTIVE

New Float Onboarding Activation (POST /{user_id}/user/activate/prequalify)

Used by the new prequalification onboarding flow. The user connects their bank account and debit card during prequalification (handled by the Transactions and Payments services) before explicitly calling this endpoint. Bank account and debit card presence are not re-validated here — they are already confirmed by prequalification.

User status must be PROCESSING
     │
     ▼
Save billing consent agreement (consent_key + IP + app version + approval date)
     │
     ▼
Emit first_debit_card_connected marketing attribution event
(appsflyer_id read from user record; X-Floatme-Appsflyer-Id header used as fallback)
     │
     ▼
Activate subscription (Subscription Service)
     │
     ▼
Create membership record (base tier, monthly term)
     │
     ▼
Update user status → ACTIVE

Cancellation & Cleanup Flow

Account cancellation is handled by POST /{user_id}/user/close-account (or POST /{user_id}/user/cancel, which calls the same logic). The flow is split into a synchronous phase and an asynchronous cleanup phase.

Synchronous Phase

POST /{user_id}/user/close-account
     │
     ▼
User status is BANNED? ──Yes──► Return 200 (no-op)
     │ No
     ▼
Has active float?
     ├── Yes → skip cleanup (debit card + Plaid items left in place)
     └── No  → proceed with full cleanup
     │
     ▼
Write membership record (status: CANCELLED, event: CLOSEACCOUNT)
     │
     ▼
Update user status → PAUSED
     │
     ▼
If cleanup enabled:
  ├── Send user_id to cleanup SQS queue
  └── Delete debit card (Payment Service) — failure is logged, not fatal
     │
     ▼
Send cancellation notification (Segment)
     │
     ▼
Return 200 OK
When a user has an active float at cancellation, the debit card and Plaid items are intentionally left in place so the float can still be collected. Cleanup is skipped for these users.

Asynchronous Cleanup Phase

The prod-user-service-cleanup Lambda consumes messages from the prod-user-service-cleanup SQS queue. Each message contains a user_id. The three cleanup steps run sequentially; all errors are collected but processing continues regardless of individual failures.

SQS message received (user_id)
     │
     ▼
1. Remove Plaid items
   │  List all active items (Transactions Service)
   │  For each item: RemoveItem
   │  └── If 412 (user has active float): skip silently
     │
     ▼
2. Block Auth0 account
   │  Prevents user from logging in
     │
     ▼
3. Schedule Array cleanup (Entitlements Service)
   │  Expects 201 Created or 404 Not Found

Cancellation Source Tracking

The membership_event_source field on the resulting membership record is set based on the caller identity:

Caller Event Source

Admin API

MX

Ollie (internal tool)

Ollie

User Service (self)

typeform (legacy label)

Subscription Service

(empty — inherits from previous record)

In-app (direct user action)

in app

Unknown

unknown