Infrastructure

All infrastructure is AWS, defined in Terraform under deploy/ and applied per target: test in the old shared account (us-west-2), test in its own account (us-east-2, legacy tables in us-east-1), staging (us-east-2) and prod (us-east-2). Each target supplies the legacy-table region and the bucket the Lambda packages are uploaded to (legacy_region, artifact_bucket). Resource names carry the {env} prefix, except the shared us-east-1 tables, which have none; the application identifier is user-service. The {env}-user-service-users DynamoDB table and Kinesis stream are defined in the infrastructure repo (aws/user-service/) and consumed here as data sources. Terraform is applied by the {env}-github-services-deploy role from the infrastructure repo, which can only create IAM roles that carry the {env}-github-services-boundary permissions boundary; the Lambda module applies it by default and hand-written roles set permissions_boundary explicitly.

Lambda Functions

Function Trigger Timeout VPC Key IAM Permissions

{env}-user-service-api

API Gateway {env}-user-service (ANY /{proxy+}, AWS IAM)

300s, 256 MB (512 MB in test)

Private subnets, PrivateSG

DynamoDB R/W on the users table + indexes, user-balance-settings, the settlement participants table + indexes; R/W on user-phone-numbers + indexes and access-codes; invoke Payments, Subscriptions, Float, TXN, Insight, Admin, Underwriting; sqs:SendMessage on the cleanup and SMS queues; events:PutEvents on the default bus; 10 vendor secrets

{env}-user-service-kinesis-feeder

DynamoDB Streams on the users table (LATEST)

840s

Private subnets, PrivateSG

kinesis:PutRecord(s) on {env}-user-service-users; stream read on the users table

{env}-user-service-cleanup

SQS {env}-user-service-cleanup (batch 10)

810s (90% of the queue visibility timeout)

Private subnets, PrivateSG

Invoke TXN and Entitlements; DynamoDB R/W on the users table and user-phone-numbers; Auth0, Iterable, AppsFlyer, Segment, RDS replica, GrowthBook secrets

{env}-user-service-subscriptions

Kinesis {env}-subscriptions (see Kinesis)

840s

Private subnets, PrivateSG

Kinesis read; DynamoDB R/W on the users table, user-phone-numbers and the settlement participants table; RDS main/replica and Segment secrets

{env}-user-service-webhook-handler

API Gateway (webhooks gateway, infrastructure repo)

840s

Private subnets, PrivateSG

sqs:SendMessage on {env}-typeform-events; Typeform secret

{env}-user-service-typeform

SQS {env}-typeform-events (batch 10)

840s

none

Invoke the user-service and Float gateways; Segment secret

{env}-user-service-survey-webhook-handler

API Gateway (webhooks gateway, infrastructure repo)

840s

none

dynamodb:PutItem on the users table; Tally secret

{env}-user-service-payment-worker

Kinesis {env}-payments (see Kinesis)

840s

none

Kinesis read; invoke the user-service gateway

{env}-user-service-floatmetrics

SQS {env}-floatmetrics-queue (batch 10, 1s batching window, ReportBatchItemFailures)

27s (90% of the queue visibility timeout)

none

dynamodb:PutItem on {env}-floatmetrics

{env}-user-service-sms-worker

SQS {env}-user-service-sms (batch 1, ReportBatchItemFailures)

30s

none

SQS receive/delete on {env}-user-service-sms; Twilio secret

Memory is the module default unless listed. Every Lambda runs the Datadog extension (ddlambda). VPC-attached Lambdas use the shared FloatMe VPC private subnets and PrivateSG security group; the lookups accept both the CFN-era tag names and the floatme-{env}-… names.

API Gateways

Gateway Routes Notes

{env}-user-service (HTTP API)

ANY /{proxy+} → {env}-user-service-api, AWS IAM (SigV4)

30s integration timeout; access logs to API-Gateway-Execution-Logs_user-service/{env} (7-day retention); VPC link user-service-{env}-api. Also mapped to user-service.{env}.svc.floatme.io, an alias record in the infrastructure repo’s per-environment zone using its wildcard certificate; the execute-api endpoint stays enabled. Callers: FloatMe backend services, the global FloatMe API Gateway, Cloudflare Workers (edge repo) as the {env}-cloudflare-workers IAM user (test, prod) or the staging-cloudflare-workers-role role (staging)

{env}-floatmetrics-api (HTTP API)

POST / (JWT authorizer: Auth0 issuer, audience https://api.floatme.io) and POST /anon/track (AWS IAM) → SQS-SendMessage on {env}-floatmetrics-queue

Direct SQS integration, no Lambda; the JWT route forwards the Authorization header as the Signature message attribute; access logs kept 1 day. Also mapped to floatmetrics.{env}.svc.floatme.io the same way as the main API; the execute-api endpoint stays enabled

SQS Queues

Queue Visibility Timeout Max Receive DLQ Purpose

{env}-user-service-cleanup

900s

5

Yes

Janitor messages from the API (account closure, bans, data deletion) — Janitor

{env}-typeform-events

900s

5

Yes

Typeform webhook payloads from the webhook-handler, consumed by the typeform Lambda

{env}-floatmetrics-queue

30s

5

Yes

App metric events from the floatmetrics gateway and the user_floatmetrics_event EventBridge rule

{env}-user-service-sms

180s

1

Yes

Access-code SMS requests from the API, consumed by the sms-worker — SMS delivery

Each DLQ is named <queue>-dlq.

Kinesis

Stream Direction Consumer configuration

{env}-payments (Payments Service)

consumed

payment-worker; filter data.type in FLOAT_DEBIT_CHARGED_BACK, SUBSCRIPTION_CHARGED_BACK; batch 100; parallelization factor 2; LATEST; bisect on error; ReportBatchItemFailures

{env}-subscriptions (Subscription Service)

consumed

subscriptions Lambda; filter data.type in payment-failed, pause-skipped, pause-resume, cancel-successful, account-closed; batch 100; LATEST; bisect on error

{env}-user-service-users (infrastructure repo, KMS-encrypted)

published

Written by the kinesis-feeder; contracts in Events

DynamoDB Tables

Schemas and access patterns are in DynamoDB Tables.

Table Region Notes

{env}-user-service-users

current region (users_table_region)

Single table, PK/SK, GSI1–GSI5 (KEYS_ONLY), point-in-time recovery, deletion protection, stream NEW_AND_OLD_IMAGES (infrastructure repo)

{env}-user-service-settlement-participants

current region (users_table_region)

Settlement participants, PK/SK, GSI1–GSI4 (KEYS_ONLY), point-in-time recovery, deletion protection, no stream (infrastructure repo); API reads USR_SETTLEMENT_DYNAMO_TABLE, subscriptions reads SETTLEMENT_DYNAMO_TABLE; schema in DynamoDB Tables

{env}-floatmetrics

current region

App metric events, 2-day TTL

user-phone-numbers

us-east-1 in prod; current region in test

Phone number per user, phone_number-index GSI

access-codes

us-east-1 in prod; current region in test

SMS access codes keyed by phone number

user-balance-settings

us-east-1 in prod; current region in test

Low-balance alert threshold per user

The us-east-1 tables carry no environment prefix. Their client resolves the region from USR_LEGACY_DYNAMO_REGION (API) or LEGACY_DYNAMO_REGION (cleanup, subscriptions); the users table from USR_DYNAMO_REGION/USR_DYNAMO_TABLE or DYNAMO_REGION/DYNAMO_TABLE.

RDS

The API, subscriptions and cleanup Lambdas connect to the shared FloatMe PostgreSQL instance (ebdb database) through the {env}/rds/main (writes) and {env}/rds/replica (reads) secrets. Schema in PostgreSQL Schema.

EventBridge

Rule Routing

user_floatmetrics_event (detail-type)

Default bus → SQS {env}-floatmetrics-queue; the queue policy allows the rule to send

The API Lambda holds events:PutEvents on the default bus and creates an event bus client named user-service.api; no code path publishes through it.

Secrets Manager

Secret Contents Consumers

{env}/rds/main

RDS main credentials

api, subscriptions

{env}/rds/replica

RDS read-replica credentials

api, subscriptions, cleanup

{env}/segment

Segment write key / access token

api, subscriptions, cleanup, typeform

{env}/auth0

Auth0 Management API credentials

api, cleanup

{env}/growthbook

GrowthBook SDK key and host

api, cleanup

{env}/castle

Castle API key

api

{env}/iterable

Iterable API key

api, cleanup

{env}/appsflyer

AppsFlyer S2S and API keys

api, cleanup

{env}/floatme/zendesk

Zendesk agent email and API token

api

{env}/floatme/fin

api_secret that signs the Intercom Fin JWT

api

{env}/typeform

Typeform webhook secret

webhook-handler

{env}/tally

Tally webhook secret

survey-webhook-handler

{env}/twilio

Twilio account_sid, auth_token, from_number

sms-worker

{env}/datadog/terraform

Datadog API and app keys

Terraform (SLOs, service definition)

The API exits at startup when the Fin secret is empty or when the Zendesk credentials are empty; the sms-worker exits when any Twilio field is empty.

Environment Configuration

Behaviour toggles and their per-environment values are catalogued in Feature Flags. Fixed integration parameters set by Terraform; staging takes the test value:

Variable test prod Effect

KYC_TEMPLATE_ID

idvtmp_2rJ5jth9wBR2dC

idvtmp_7XqxmJjEinKiis

Plaid IDV template passed to the Transactions Service

ITERABLE_SMS_MESSAGE_TYPE_ID

196723

197885

Iterable message type the SMS opt-in endpoint subscribes users to

APPSFLYER_IOS_APP_ID

id6451323926

id1395667279

AppsFlyer iOS app (Android io.floatme.floatmeapp in both)

CASTLE_URL

https://api.castle.io/v1

https://api.castle.io/v1

Castle API

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, each 99.9% target / 99.99% warning over 7-day and 30-day windows:

  • Lambda Errors — api, kinesis-feeder, cleanup, subscriptions, webhook-handler, typeform, survey-webhook-handler

  • Lambda Throughput — kinesis-feeder, cleanup, webhook-handler, typeform, survey-webhook-handler

  • Lambda Latency — api, kinesis-feeder, cleanup, subscriptions, webhook-handler, typeform, survey-webhook-handler

The service definition (dd-service: user-service, tier 1, team devops) links to this repo and to https://docs.floatme.io/user-service.