Architecture

System Context

The Subscription Service is deployed as a set of AWS Lambda functions and owns the full subscription billing lifecycle for the FloatMe platform. It exposes a REST API behind API Gateway and participates in the broader FloatMe platform as both an event-driven consumer (Kinesis streams, SQS queues, EventBridge rules) and producer (EventBridge, Kinesis). Inbound membership lifecycle events arrive via Kinesis from the User Service. ACH payment outcomes arrive via a second Kinesis stream from the Payments Service. Scheduled CloudWatch rules drive collection runs on weekdays and the daily notification pass. EventBridge routes income and balance events from the Insight and Transactions services to SQS queues consumed by the webhook collection workers.

system context

Inbound Traffic

Source Description

site-subscriptions (API Gateway)

IAM-authenticated API Gateway consumed by internal FloatMe services and the mobile app via the global API gateway. All routes use AWS_IAM authorization. The chi router rejects paths not declared in spec/subscriptions.yaml.

site-subscription-service-collections-scheduled (SQS)

Work queue consumed by the collections-worker Lambda for scheduled collection runs. Messages are enqueued by the collections-job Lambda after querying DynamoDB for due subscriptions. Visibility timeout 60 s, DLQ after 5 receives.

site-subscription-service-collections-retry (SQS)

Work queue consumed by the collections-worker for subscriptions in a retry-eligible state. Enqueued by the collections-job on the Monday–Friday retry run (07:00 UTC). Visibility timeout 60 s, DLQ after 5 receives.

site-subscription-service-collections-pause (SQS)

Work queue consumed by the collections-worker for subscriptions in a pause-eligible state. Enqueued by the collections-job on the Monday–Friday pause run (22:00 UTC). Visibility timeout 60 s, DLQ after 5 receives.

site-subscription-service-income-event-tap (SQS)

Income detection events from the Insight Service, filtered and routed via an EventBridge rule (income_txn events where amount < −$75). Consumed by the webhook-worker Lambda. Visibility timeout 900 s, DLQ after 1 receive.

site-subscription-service-balance-event-tap (SQS)

Balance update events from the Transactions Service, filtered and routed via an EventBridge rule (new_account events where the main account has a non-negative balance). Consumed by the webhook-balance-worker Lambda. Visibility timeout 900 s, DLQ after 1 receive.

site-subscription-service-batch-worker (SQS)

Batch operation requests consumed by the batch-worker Lambda (batch size 10, max concurrency 5). Visibility timeout 60 s.

site-subscription-service-pre-subscription-notifier-worker (SQS)

Notification jobs enqueued by the notifier-scheduler Lambda (batch size 10, max concurrency 5). Visibility timeout 900 s, DLQ after 5 receives.

site-user-service-users (Kinesis)

Membership lifecycle events from the User Service, filtered to event types: UPGRADE, DOWNGRADE, CANCEL, RETRACT, AUTODOWNGRADED, GONETOCOLLECTIONS, PAYNOW, SUB_PAUSED, UNPAUSE, UNPAUSE_CHARGE, CLOSEACCOUNT, REACTIVATE. Consumed by the memberships Lambda.

site-payments (Kinesis)

ACH payment outcome events from the Payments Service, filtered to subscription debit types: SUBSCRIPTION_COMPLETED, SUBSCRIPTION_RETURNED, SUBSCRIPTION_REFUNDED, SUBSCRIPTION_CHARGED_BACK. Consumed by the ach-handler Lambda.

EventBridge (collections schedule)

Three CloudWatch rules invoke the collections-job Lambda on weekdays: scheduled run at 08:00 UTC, retry run at 07:00 UTC, pause run at 22:00 UTC. Each rule injects a detail.process field (scheduled, retry, or pause) to steer the job’s query.

EventBridge (notifier cron)

A daily CloudWatch rule (cron(0 12 ? * * *)) invokes the notifier-scheduler Lambda at 12:00 UTC every day (production only).

Outbound Traffic

Destination Description

site-subscriptions (Kinesis)

Billing activity change events published by the kinesis-feeder Lambda from the billing-activity DynamoDB Stream. Consumed by downstream services that need subscription status updates.

Payments Service

Collection attempt requests sent by the collections-worker and webhook workers. Disbursement requests sent by the API Lambda on subscription activation. All calls are IAM-authenticated API Gateway requests.

User Service

User profile and identity lookups performed by the API Lambda, memberships Lambda, and notifier-worker. IAM-authenticated API Gateway calls.

Underwriting Service

Eligibility and plan availability queries made by the API Lambda during subscription activation and plan upgrade/downgrade flows.

Transactions Service

Bank account balance lookups performed by the collections-worker and webhook workers to inform collection routing decisions. Also consumed by the notifier-worker.

Insight Service

Source of income_txn events routed through EventBridge to the income-event-tap SQS queue. Not called directly — integration is inbound-only via EventBridge.

Admin Service

Admin note attachment calls made by the ach-handler Lambda during ACH payment outcome processing.

Segment

User analytics events emitted by the ach-handler (ACH outcomes), collections-worker (collection outcomes), and notifier-worker (pre-subscription notifications). API key fetched from Secrets Manager.

Iterable

Email and in-app notifications triggered by the ach-handler (ACH payment events) and notifier-worker (pre-subscription notifications). API key fetched from Secrets Manager.

AppsFlyer

Mobile attribution events emitted by the ach-handler and webhook-balance-worker for subscription payment events. App IDs differ between production and non-production environments.

SageMaker

ML model endpoint (subcollection-inference-endpoint-site) invoked by the collections-worker via the mldecider package to determine per-subscriber collection eligibility.

Lambda Functions

Function Trigger Responsibility

site-subscriptions-api

API Gateway (IAM)

Primary REST API for all subscription operations. Handles activation, reactivation, upgrade, downgrade, manual collection (pay), billing history, billing details, active term, user bans, and QA subscription creation. Reads and writes billing-activity and billing-activity-history. Acquires a DynamoDB distributed lock before mutating subscription state. Queries the Underwriting Service for eligibility on plan changes and the Payments Service for disbursement and collection. Emits analytics events to Segment and AppsFlyer on key lifecycle transitions.

site-subscriptions-memberships

Kinesis (site-user-service-users)

Consumes membership lifecycle events from the User Service Kinesis stream. Filters for twelve event types — including UPGRADE, DOWNGRADE, CANCEL, RETRACT, AUTODOWNGRADED, GONETOCOLLECTIONS, PAYNOW, SUB_PAUSED, UNPAUSE, UNPAUSE_CHARGE, CLOSEACCOUNT, and REACTIVATE — and drives corresponding subscription state transitions in DynamoDB. Acquires a distributed lock per user before applying each transition to prevent concurrent modification. Queries the User Service for profile data when needed.

site-subscriptions-ach-handler

Kinesis (site-payments)

Consumes ACH payment outcome events from the Payments Service Kinesis stream. Filters for subscription debit event types (SUBSCRIPTION_COMPLETED, SUBSCRIPTION_RETURNED, SUBSCRIPTION_REFUNDED, SUBSCRIPTION_CHARGED_BACK) and maps each outcome to a billing_status transition in billing-activity. Acquires a distributed lock before writing. Emits user-facing notifications via Segment, Iterable, and AppsFlyer. Makes admin note calls to the Admin Service for certain outcomes.

site-subscriptions-kinesis-feeder

DynamoDB Stream (billing-activity)

Reads change records from the billing-activity DynamoDB Stream and publishes each record to the site-subscriptions Kinesis stream. Acts as a bridge between the internal DynamoDB state store and external consumers that need real-time subscription change events. Deployed against a legacy DynamoDB provider region, with a matching S3 bucket for the Lambda artifact in production.

site-subscriptions-batch-worker

SQS (site-subscription-service-batch-worker)

Processes bulk subscription operations and data management tasks dispatched to the batch queue. Reads and writes billing-activity and billing-activity-history. Acquires distributed locks before modifying subscription state. Calls the Payments and User services as needed. Segment analytics key available for event emission on batch outcomes.

site-subscriptions-collections-job

EventBridge (scheduled rules, Mon–Fri) + SQS paging queues

Orchestrates all three collection runs (scheduled, retry, pause) by querying billing-activity GSIs for subscriptions due for each run type. Paginates through large result sets by self-enqueueing page tokens to dedicated SQS paging queues (scheduled-collections-pages, retry-collections-pages, pause-collections-pages) and re-triggering itself for each page. Enqueues qualifying subscription IDs to the corresponding work queue (collections-scheduled, collections-retry, collections-pause). Processes up to 50 DynamoDB pages per invocation. Memory-optimized at 2048 MB.

site-subscriptions-collections-worker

SQS (collections-scheduled, collections-retry, collections-pause; max concurrency 50)

Core collection executor. Dequeues individual subscription collection jobs from any of the three collection queues. Acquires a DynamoDB distributed lock per user to prevent concurrent attempts. Invokes the SageMaker ML model via the mldecider package to determine whether collection should proceed for the subscriber. Submits an ACH collection attempt to the Payments Service. Updates billing-activity and billing-activity-history with the outcome. Re-enqueues to the retry or pause queue on failure if applicable. Emits analytics events to Segment, Iterable, and AppsFlyer on collection outcomes.

site-subscriptions-webhook-worker

SQS (site-subscription-service-income-event-tap) via EventBridge

Responds to income detection events sourced from the Insight Service and routed through EventBridge. Processes income_txn events where the transaction amount exceeds $75. For each event, checks whether the user has a subscription in a collectible state, validates ACH attempt limits (configurable, default 3 per month) and a minimum income threshold ($100), then submits a collection attempt to the Payments Service if all criteria are met. Acquires a distributed lock before attempting collection. Emits analytics events to Segment on outcome.

site-subscriptions-webhook-balance-worker

SQS (site-subscription-service-balance-event-tap) via EventBridge

Responds to new_account balance update events from the Transactions Service routed through EventBridge. For each event where the primary account has a non-negative available, current, or calculated-available balance, evaluates whether the user has a subscription in a collectible state and attempts opportunistic collection via the Payments Service. Acquires a distributed lock before attempting. Emits analytics events to Segment and AppsFlyer, and attribution events to AppsFlyer on success.

site-subscriptions-notifier-scheduler

EventBridge (daily, 12:00 UTC) + SQS paging queue

Daily scheduler that finds users approaching their subscription billing date within the notification window. Queries billing-activity via a GSI for subscriptions in the SCHEDULED status. Pages through results by self-enqueueing page tokens to the pre-subscription-notifier-scheduler SQS queue and re-triggering itself for each page. Enqueues qualifying user records to the notifier-worker queue. Runs in production only. Processes up to 50 DynamoDB pages per invocation.

site-subscriptions-notifier-worker

SQS (site-subscription-service-pre-subscription-notifier-worker; max concurrency 5)

Processes individual pre-subscription notification jobs enqueued by the notifier-scheduler. For each user, queries the User Service for profile data and the Payments and Transactions services as needed. Evaluates GrowthBook feature flags to determine notification channel eligibility. Sends notifications via Iterable. GrowthBook and Iterable API keys are loaded from Secrets Manager at startup.

Collections Architecture

The collections engine uses three parallel processing paths — scheduled (weekday runs at multiple times), webhook income-triggered, and webhook balance-triggered — all funneling into the Payments Service for execution. The collections-job Lambda orchestrates the scheduled paths using a paging pattern that allows it to fan out across arbitrarily large subscription populations within Lambda’s execution time limits. All three collection paths acquire a DynamoDB distributed lock per user before attempting collection, preventing concurrent debit attempts for the same subscriber. The collections-worker consults a SageMaker ML model via the mldecider package before each scheduled attempt to determine whether collection is likely to succeed.

collections subsystem

See Collections Engine for the detailed flow diagrams and decision logic for each collection path, including retry sequencing, pause handling, and the ML decider integration.

Data Storage

DynamoDB: billing-activity

The primary store for subscription records. Holds one row per subscription billing term per user, tracking the full lifecycle from SCHEDULED through collection attempts to COMPLETED, ERROR, or WAIVED. Both the API Lambda and all collection Lambdas read and write to this table. A DynamoDB Stream on this table feeds the kinesis-feeder Lambda.

Attribute           Description
────────────────────────────────────────────────────────────────────
user_id             Owning user (partition key)
billing_date        Subscription due date (sort key)
subscription_id     UUID identifying the subscription term
billing_amount      Amount to collect in cents
billing_status      Current status (SCHEDULED, ACHSENT, COMPLETED, ERROR, WAIVED)
billing_period      MM/YYYY representation of billing_date
process             Last process that updated the record (INITIAL, RETRY, WEBHOOK, etc.)
transaction_id      Payment reference from last collection attempt
created_date        Record creation timestamp
completion_date     Timestamp when billing reached a terminal state
initial_run_date    Timestamp of first collection attempt
last_run_date       Timestamp of last update (used as sort key in history table)
usio_error          Payment error detail from last attempt; cleared on success
term                Subscription plan term

DynamoDB: billing-activity-history

Append-only audit log of every state transition for every billing record. Mirrors the schema of billing-activity but uses user_id + last_run_date as the primary key, allowing multiple history rows per user per subscription term. Every write to billing-activity is accompanied by a corresponding write to this table. The last_run_date field must be updated before writing to avoid overwriting prior history entries.

DynamoDB: locks

Distributed per-user collection locks. All collection paths — scheduled, retry, pause, webhook income, webhook balance — and the memberships and ach-handler Lambdas acquire a lock from this table before modifying subscription state. Prevents concurrent debit attempts for the same user.

Table Purpose

billing-activity

Primary subscription records. One row per billing term per user. Queried by GSI for collection runs and notification scheduling.

billing-activity-history

Append-only billing state transition history. PK: user_id + last_run_date. Used for audit and reporting.

locks

Distributed per-user locking table. Shared across all Lambdas that mutate subscription state.

See DynamoDB Tables for the full attribute reference, GSI definitions, and access pattern details.

External Service Integrations

Service Integration

Payments Service

Core payment execution layer. The API Lambda submits disbursements on subscription activation. The collections-worker and webhook workers submit ACH collection attempts. The ach-handler processes payment outcome callbacks arriving on the site-payments Kinesis stream. All calls are IAM-authenticated API Gateway requests.

User Service

Queried by the API Lambda for user profile data on subscription operations, by the memberships Lambda on lifecycle events, and by the notifier-worker before sending pre-subscription notifications. IAM-authenticated API Gateway calls.

Underwriting Service

Queried by the API Lambda during subscription activation and plan upgrade/downgrade to validate eligibility and retrieve approved plan details.

Transactions Service

Queried by the collections-worker and webhook workers for current bank account balance data to inform collection routing decisions. Also called by the notifier-worker. IAM-authenticated API Gateway calls.

Insight Service

Source of income_txn events. The Insight Service publishes income detection events to EventBridge; an EventBridge rule filters for detail.amount < -7500 (cents, i.e. deposits of $75.00 or more, represented as negative values) and routes matching events to the income-event-tap SQS queue consumed by the webhook-worker Lambda. Not called directly by the service.

Admin Service

Internal admin API called by the ach-handler Lambda to attach notes to user accounts when processing certain ACH payment outcome events.

GrowthBook

Feature flag service consulted by the collections-worker, webhook workers, and notifier-worker. Controls collection eligibility gating, ACH routing decisions, ML decider activation, and notification channel selection. SDK key loaded from Secrets Manager at Lambda startup.

SageMaker

ML model endpoint (subcollection-inference-endpoint-site) invoked by the collections-worker via the mldecider package. The model returns a collection eligibility signal that gates whether a scheduled collection attempt proceeds.

Segment

User analytics platform. Events emitted by the ach-handler (ACH payment outcomes), collections-worker (collection results), webhook workers (webhook-triggered collection outcomes), and notifier-worker (pre-subscription notification events). Write key loaded from Secrets Manager.

Iterable

Email and in-app notification delivery. Events triggered by the ach-handler for ACH payment outcomes and by the notifier-worker for pre-subscription notifications. API key loaded from Secrets Manager.

AppsFlyer

Mobile attribution tracking. Events emitted by the ach-handler and webhook-balance-worker for subscription payment events. App ID differs between production (id1395667279) and non-production (id6451323926) environments. API key loaded from Secrets Manager.

  • DynamoDB Tables — Full attribute reference, GSI definitions, and access patterns for billing-activity, billing-activity-history, and locks

  • Infrastructure — Terraform layout, Lambda configuration, SQS queue definitions, Kinesis streams, EventBridge rules, and Secrets Manager paths

  • Collections Engine — Detailed collection flow diagrams and decision logic for all three collection paths

  • ACH Processing — Kinesis ACH callback handling, billing status mapping, and notification emission

  • Memberships — Membership Kinesis consumer, event types, and subscription state transition details

  • Notifications — Three-day pre-subscription notifier scheduler and worker

  • Subscription Lifecycle — Billing statuses, state transitions, and key API flows

  • Event Flows — Published events, EventBridge rules, and scheduled job flows

  • API Specification — Full OpenAPI spec (Swagger UI)