Skip to content

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: UsernameExistsExceptionDuplicateRecordError → 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:

  1. Account: pk=ACCOUNT#{visible_account_id}, sk=DETAILS — includes system_account_id, stripe_id, cell_id, limits
  2. Membership (dual-record pattern):
  3. pk=USER#{cognito_username}, sk=ACCOUNT#{visible_account_id} — role=OWNER, status=ACTIVE, system_account_id, cloud_provider
  4. pk=ACCOUNT#{visible_account_id}, sk=USER#{cognito_username} — role=OWNER, status=ACTIVE, email, signup_method
  5. 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_signupcomponents/identity_service/

  1. cognito_idp.confirm_sign_up() — validates the 6-digit code
  2. cognito_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

  1. Cognito redirects to Google OAuth (callback URL includes console hostname)
  2. PreSignUp Lambda fires on PreSignUp_ExternalProvider trigger
  3. Links Google identity to existing Cognito user (if exists) or creates new
  4. Sets autoConfirmUser=true, autoVerifyEmail=true — no email confirmation needed
  5. 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_ERROR
  • EMAIL_PRESIGNUP_SUCCESS, EMAIL_PRESIGNUP_FAILURE
  • INCONSISTENT_ACCOUNT_STORES, INCONSISTENT_USER_STORES
  • SIGNUP_CONFIRM_SUCCESS, SIGNUP_CONFIRM_ERROR