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
| Status | Trigger | Description |
|---|---|---|
|
Account creation |
Default status set when the Auth0 account is first created. User cannot take out floats and is not billed. |
|
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. |
|
Cancellation or MX action |
Subscription has ended. User can log in but is shown a reactivation screen. Not billed. |
|
MX action |
User is flagged for review. Cannot take out floats. Not billed. MX can clear this status. |
|
MX action or chargeback |
User is blocked from logging in. Triggered automatically when a |
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.
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. It can be triggered explicitly via POST /{user_id}/user/activate or inline during the Plaid Layer signup flow.
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
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 |
|
Ollie (internal tool) |
|
User Service (self) |
|
Subscription Service |
(empty — inherits from previous record) |
In-app (direct user action) |
|
Unknown |
|
Related Pages
-
Users & Data Model — User status values and DynamoDB schema
-
Reactivation — How users return to active status
-
Membership Upgrades — Upgrading tier mid-cycle
-
Event-Driven Flows — Chargeback detection (payment worker) and the outbound Kinesis events produced by status changes