Infrastructure

All infrastructure is AWS, defined in Terraform under deploy/ and managed per environment. Resource names are shown with the site prefix (test or prod, e.g. prod-subscription-service-api). The application identifier is subscription-service. Every resource carries the provider default tags floatme:environment, floatme:application, floatme:managed-by (terraform) and floatme:repository (floatme-corp/subscription-service), alongside the unprefixed environment and application keys.

Deployment

Every environment is one call of the shared .github/workflows/deploy-env.yaml, which takes the account, regions, state bucket and artifact buckets as inputs. Pull requests (on_pr.yaml) build once and plan each continuous environment from that build with the read-only site-github-services-plan role; pushes to main (on_main.yaml) build once and apply the same artifacts to each continuous environment with site-github-services-deploy; published releases (on_release.yaml) apply prod from the release’s artifacts. Authentication is GitHub OIDC; there are no static credentials. Both roles, and the site-github-services-boundary permissions boundary every Lambda role carries, are owned by the infrastructure repo. The Terraform providers use the workflow’s credentials directly, with no further role assumption.

Target Account Region Legacy region State bucket Artifact buckets (primary / legacy)

test (old account)

267052520423

us-west-2

us-west-2

floatme-test-media

floatme-test-media / floatme-test-media

test

299185625033

us-east-2

us-east-1

floatme-test-services-terraform-state

floatme-test-services-artifacts / floatme-test-services-artifacts-legacy

staging

208295047603

us-east-2

us-east-1

floatme-staging-services-terraform-state

floatme-staging-media / floatme-staging-media-legacy

prod

267052520423

us-east-2

us-east-1

floatme-prod-media

floatme-prod-media / floatme-prod-media-legacy

State is keyed terraform/subscription-service/ in each state bucket and locked by the terraform-locking table in each account. Lambda packages upload to the artifact bucket (artifact_bucket); the kinesis-feeder Lambda, which runs in the legacy region next to the DynamoDB stream, uploads to the legacy artifact bucket (legacy_artifact_bucket). deploy/.terraform.lock.hcl pins the providers.

Peer service APIs (admin, user-service, txn, insight, underwriting, entitlements) are resolved by name at plan time and addressed by convention (https://{id}.execute-api.{region}.amazonaws.com). A peer that has not yet deployed to an environment yields an empty URL and an account-wide execute-api:Invoke grant until the next deploy picks up its id, so a fresh environment comes up in two passes rather than failing the plan. The payments API is a REST gateway with no list-by-name lookup, so payments-service must deploy to an environment before this service.

Lambda Functions

Function Trigger Timeout Memory Key IAM Permissions

site-subscription-service-api

API Gateway (site-subscription-service, AWS IAM)

900s

512 MB

DynamoDB R/W/delete (billing-activity, billing-activity-history, locks); invoke Payments, Users, TXN, Underwriting; Segment, GrowthBook, AppsFlyer secrets

site-subscription-service-collections-worker

SQS collections-scheduled / collections-retry / collections-pause; max concurrency 50

54s

default

DynamoDB R/W (all three tables); SQS on the three collection queues; invoke TXN, Payments, Users, Insight; SageMaker InvokeEndpoint; Segment, GrowthBook, Iterable, AppsFlyer secrets

site-subscription-service-collections-job

EventBridge rules (EventBridge); SQS paging queues (batch size 1)

810s

2048 MB

SQS SendMessage (three collection queues); SQS full access on paging queues; DynamoDB Query (billing-activity)

site-subscription-service-memberships

Kinesis site-user-service-users (Kinesis Streams)

840s

default

Kinesis read (users stream); DynamoDB R/W + BatchWriteItem (all three tables); invoke Users

site-subscription-service-ach-handler

Kinesis site-payments (Kinesis Streams)

840s

default

Kinesis read (payments stream); DynamoDB R/W + BatchWriteItem (billing-activity, billing-activity-history); invoke Users, Admin, TXN; Segment, Iterable, AppsFlyer secrets

site-subscription-service-kinesis-feeder

DynamoDB Stream on billing-activity

840s

default

Kinesis PutRecord(s) (site-subscriptions); DynamoDB stream read; deployed in the table’s region via a dedicated provider alias

site-subscription-service-batch-worker

SQS site-subscription-service-batch-worker; max concurrency 5

54s

default

DynamoDB R/W (all three tables); SQS on its queue; invoke Payments, Users; Segment secret

site-subscription-service-webhook-balance-worker

SQS site-subscription-service-balance-event-tap; max concurrency 5

810s

default

DynamoDB R/W (all three tables); SQS on its queue; invoke Payments, Users; Segment, GrowthBook, AppsFlyer secrets

site-subscription-service-notifier-scheduler

EventBridge pre_subscription_notifier (EventBridge); SQS paging queue (batch size 1)

810s

default

DynamoDB Query (billing-activity); SQS on both notifier queues

site-subscription-service-notifier-worker

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

810s

default

Invoke TXN, Payments, Users; SQS on its queue; Iterable, GrowthBook secrets

Timeouts are set in deploy/lambda.tf (SQS-consuming Lambdas run at 90% of their queue’s visibility timeout). Kinesis consumers use batch size 100 (var.kinesis_stream_batch_size), starting position LATEST, and BisectBatchOnFunctionError; the ach-handler additionally sets parallelization factor 2 and ReportBatchItemFailures.

Container Images

The subscription-jobs image (cmd/subscription-jobs/Dockerfile, distroless) is built once per push to main (see Deployment) and pushed to ghcr.io/floatme-corp/subscription-service/subscription-jobs tagged v0.0.{short sha}-alpha and latest. Each apply copies that tag into the account’s site-subscriptions ECR repository — owned by the infrastructure repo, alongside the site-subscriptions ECS cluster — with crane copy, digest unchanged. When release-please creates a release the same manifest also gets the release tag, which the prod apply copies. Alpha images on ghcr beyond the newest ten are deleted after each main apply; release-tagged ones are kept. The image is the entrypoint for scheduled batch jobs run as ECS Fargate tasks; no task definitions reference it yet, and its only subcommand is version. Local builds: make container-build-subscription-jobs.

DynamoDB Tables

Schemas, access patterns, and enums are in DynamoDB Tables.

Table Region Streams Notes

billing-activity

dynamo_legacy_region variable (us-east-1 in prod)

Yes

Primary subscription state; the stream drives kinesis-feeder → site-subscriptions Kinesis

billing-activity-history

dynamo_legacy_region variable (us-east-1 in prod)

No

History records written alongside billing-activity

locks

dynamo_legacy_region variable (us-east-1 in prod)

No

Distributed locks (schema); acquired by all collection Lambdas

The three tables carry no environment prefix; a dedicated aws.dynamodb provider alias deploys resources and reads data sources in their region.

SQS Queues

Queue Visibility Max Receive DLQ Purpose

site-subscription-service-collections-scheduled

60s

5

Yes

Scheduled collection jobs from collections-job; consumed by collections-worker (batch 10)

site-subscription-service-collections-retry

60s

5

Yes

Retry collection jobs; consumed by collections-worker (batch 10)

site-subscription-service-collections-pause

60s

5

Yes

Pause-state collection jobs; consumed by collections-worker (batch 10)

site-subscription-service-scheduled-collections-pages

900s

5

Yes

collections-job continuation messages for the scheduled run’s paginated scan

site-subscription-service-retry-collections-pages

900s

5

Yes

collections-job continuation messages for the retry run

site-subscription-service-pause-collections-pages

900s

5

Yes

collections-job continuation messages for the pause run

site-subscription-service-batch-worker

60s

—

No

Manual batch collection requests; consumed by batch-worker (batch 10)

site-subscription-service-balance-event-tap

900s

1

Yes

Balance events from EventBridge (EventBridge); consumed by webhook-balance-worker (batch 10; 20s delivery delay in test)

site-subscription-service-pre-subscription-notifier-scheduler

900s

5

Yes

notifier-scheduler paging queue (batch 1)

site-subscription-service-pre-subscription-notifier-worker

900s

5

Yes

Per-user notification jobs; consumed by notifier-worker (batch 10)

Each DLQ is named <queue>-dlq (900s visibility). The collections-job paging queues let each run continue a paginated DynamoDB scan across invocations: an invocation processes up to PAGES_PER_INVOKE pages, then sends itself a continuation message.

Kinesis Streams

Stream Direction Role

site-user-service-users

Inbound

Membership lifecycle events from the User Service; consumed by memberships, filtered to 12 event types (event filter)

site-payments

Inbound

ACH settlement events from the Payments Service; consumed by ach-handler, filtered to 4 subscription event types (ACH callbacks)

site-subscriptions

Outbound

Subscription change feed for downstream services; kinesis-feeder publishes every INSERT/MODIFY billing-activity DynamoDB stream record to it; REMOVE records are skipped (Events)

EventBridge

Rule Schedule / Pattern Enabled Purpose

subscription-collections-scheduled

cron(0 8 ? * MON-FRI *) (08:00 UTC)

prod only

Triggers collections-job with process scheduled (collections.adoc#scheduled)

subscription-collections-retry

cron(0 7 ? * MON-FRI *) (07:00 UTC)

prod only

Triggers collections-job with process retry (collections.adoc#retry)

subscription-collections-pause

cron(0 22 ? * MON-FRI *) (22:00 UTC)

always

Triggers collections-job with process pause (collections.adoc#pause)

pre_subscription_notifier

cron(0 12 ? * * *) (12:00 UTC daily)

prod only

Triggers notifier-scheduler with status SCHEDULED (Notifications)

site-subscription-service-balance-detected

Pattern: new_account from txn-service.feeder, detail.is_main: true, any of available/current/calc_available ≥ 0

always

Routes balance events to the balance-event-tap queue for the webhook-balance-worker (collections.adoc#webhook-balance)

Secrets Manager

Secrets are namespaced by environment (site/…​).

Secret Purpose Consumers

site/segment

Segment write key

api, collections-worker, ach-handler, webhook-balance-worker, batch-worker

site/iterable

Iterable API key

ach-handler, notifier-worker

site/growthbook

GrowthBook SDK key

api, collections-worker, webhook-balance-worker, notifier-worker

site/appsflyer

AppsFlyer API key

api, ach-handler, webhook-balance-worker

site/datadog/terraform

Datadog API/app keys

Terraform only (SLOs and service catalog)

API Gateway

The site-subscription-service API Gateway (AWS IAM / SigV4 auth) forwards all routes (ANY /{proxy+}) to the site-subscription-service-api Lambda (payload format 1.0, 30s integration timeout). Access logs go to CloudWatch Logs (/aws/api-gateway/site-subscription-service, 30-day retention). Callers are FloatMe backend services using IAM-signed requests.

The gateway is also reachable at the custom domain subscription-service.site.svc.floatme.io, an API Gateway v2 domain mapped to the stage with an alias record in the infrastructure-owned site.svc.floatme.io zone, using that zone’s wildcard ACM certificate. The default execute-api endpoint stays enabled.

Monitoring

Datadog monitors and SLOs exist in prod only (local.create_datadog_monitors in deploy/datadog.tf). Alerts outside prod go unread, and the old and new test accounts both deploy as test, so Datadog would reject the second account’s identical monitors as duplicates.

Datadog SLOs (all 99.9% target / 99.99% warning over 7-day and 30-day windows; service:subscription-service, env:site), defined in deploy/datadog.tf:

SLO Lambdas covered

[AWS][site-subscription-service] Lambda Errors SLO

collections-worker, collections-job, memberships, ach-handler, api, kinesis-feeder, notifier-scheduler, notifier-worker

[AWS][site-subscription-service] Lambda Throughput SLO

collections-job, memberships, ach-handler, api, kinesis-feeder, notifier-scheduler

[AWS][site-subscription-service] Lambda Latency SLO

ach-handler, api, kinesis-feeder, notifier-scheduler

The Datadog service catalog entry registers the service as tier 1 (team devops) with links to the source repo and the documentation site.