Flow: User Signup
New customer registration from the console web app through Cognito, Stripe, and DynamoDB.
Request Path
graph TD
A["Browser (Console React app)"]
subgraph gw["API Gateway (Console API)"]
subgraph ecs["Monolith ECS (BFF Console)"]
B["Cognito: sign_up (creates UNCONFIRMED user)"]
C["DynamoDB: upsert user record (UsersAccountsTable)"]
D["Stripe: create customer + attach payment method"]
E["DynamoDB: create account record + membership + default API key"]
end
end
F["User receives verification email (SES)"]
subgraph confirm["Browser: POST /api/account/confirm_signup"]
G["Cognito: confirm_sign_up + verify email"]
H["PostConfirmation Lambda → Slack notification"]
end
A --> B
B --> C
C --> D
D --> E
E -->|returns visible_account_id| F
F --> G
G --> H
Step-by-Step
1. Browser → Console API
Where: API Gateway {env}-ConsoleApi, route POST /api/account/signup (anonymous — no auth required)
Input: email, name, country, organization, password, Stripe token, optional UTM fields.
Inspect: Check API Gateway — see API Gateway.
2. Cognito User Creation
Where: components/identity_service/ → backends/identity_aws.py
Calls cognito_idp.sign_up() with email + password + secret hash (HMAC-SHA256).
Creates user in UNCONFIRMED state. Cognito sends verification email via SES.
Cognito trigger fired: PreSignUp Lambda (for Google SSO, links identity and auto-confirms).
Inspect: Check Cognito user — see Cognito.
aws cognito-idp admin-get-user --user-pool-id {pool_id} --username {email}
Failure: UsernameExistsException → DuplicateRecordError → upsert instead of create.
3. User Record in DynamoDB
Where: components/users_accounts_service/
Writes to UsersAccountsTable (note: no {env}- prefix in staging):
- pk:
USER#{cognito_sub}, sk:DETAILS - Fields: email, name, organization, country, email_verified=false, signup_method
Inspect: Query user record — see DynamoDB and Controller.
4. Stripe Customer Registration
Where: components/billing_service/ → data/stripe_data_service.py
Creates Stripe customer with email, name, country. Attaches payment method from token. Returns stripe_id.
Failure here is critical: User exists in Cognito but no account. Tracked by INCONSISTENT_ACCOUNT_STORES metric.
5. Account + Membership + API Key Creation
Where: components/users_accounts_service/
Multiple DynamoDB writes to UsersAccountsTable:
- Account: pk=
ACCOUNT#{visible_account_id}, sk=DETAILS— includes system_account_id, stripe_id, cell_id, limits - Membership (dual-record pattern):
- pk=
USER#{cognito_username}, sk=ACCOUNT#{visible_account_id}— role=OWNER, status=ACTIVE, system_account_id, cloud_provider - pk=
ACCOUNT#{visible_account_id}, sk=USER#{cognito_username}— role=OWNER, status=ACTIVE, email, signup_method - API Key: via
api_keys_service.create_api_key()— default key for the account
The dual membership records allow efficient lookups in both directions (user→accounts and account→users).
Returns visible_account_id to the browser.
6. Email Confirmation
Where: Browser POST /api/account/confirm_signup → components/identity_service/
cognito_idp.confirm_sign_up()— validates the 6-digit codecognito_idp.admin_update_user_attributes()— sets email_verified=true
Cognito trigger fired: PostConfirmation Lambda → Slack webhook notification (skips internal domains: marqo.ai, s2search.io, mailinator.com).
Inspect: Check PostConfirmation Lambda logs — see Lambda. Note: this Lambda has a CDK-generated name (not {env}- prefixed). Find it with:
aws lambda list-functions --query "Functions[?contains(FunctionName, 'PostConfirmation') && contains(FunctionName, 'staging')].[FunctionName]" --output text
# Then tail its logs:
aws logs tail /aws/lambda/{function-name-from-above} --since 15m
Google SSO Variation
- Cognito redirects to Google OAuth (callback URL includes console hostname)
PreSignUpLambda fires onPreSignUp_ExternalProvidertrigger- Links Google identity to existing Cognito user (if exists) or creates new
- Sets
autoConfirmUser=true,autoVerifyEmail=true— no email confirmation needed - Account creation proceeds as normal
What to Look For
| Symptom | Where to Check |
|---|---|
| Signup failing | Monolith logs (ECS). Check ECS. |
| "User already exists" | Cognito user pool. DDB user record. May need upsert. |
| No verification email | SES delivery metrics. Cognito trigger logs. Check spam folder. |
| Stripe failure | Billing service logs. INCONSISTENT_ACCOUNT_STORES metric in CloudWatch. |
| Google SSO not working | PreSignUp Lambda logs. Google OAuth config in Cognito. |
| Slack notification missing | PostConfirmation Lambda logs. Webhook URL valid? |
| Account created but can't login | Check email_verified flag in Cognito. Check user_status. |
Metrics
CloudWatch custom metrics (namespace varies by component):
SIGNUP_SUCCESS,SIGNUP_FAILURE,SIGNUP_ERROREMAIL_PRESIGNUP_SUCCESS,EMAIL_PRESIGNUP_FAILUREINCONSISTENT_ACCOUNT_STORES,INCONSISTENT_USER_STORESSIGNUP_CONFIRM_SUCCESS,SIGNUP_CONFIRM_ERROR
Related Docs
- Control Plane — Monolith, Cognito, API Gateway
- Controller — UsersAccountsTable schema
- Cognito — user pool inspection
- ECS — monolith container logs
- CloudWatch — signup metrics
- Login — what happens after signup