Full Stack • Java • System Design • Cloud • AI Engineering

Customer Onboarding System Design

Design a scalable enterprise Customer Onboarding System — covering multi-channel intake, identity verification, KYC/AML compliance, account provisioning, product enrollment, welcome journey orchestration, progressive profiling, and regulatory audit trail.

1-Hour Interview Roadmap

Time Topic
0 – 5 min Requirements clarification
5 – 10 min Capacity estimation
10 – 18 min High-level architecture + core services
18 – 28 min Onboarding lifecycle — registration through account activation
28 – 36 min Identity verification + KYC/AML compliance
36 – 43 min Account provisioning + product enrollment
43 – 50 min Welcome journey orchestration + progressive profiling
50 – 56 min Database design + API design
56 – 60 min Trade-offs + common interview mistakes

What Are We Building?

An enterprise Customer Onboarding System (COS) that orchestrates the complete journey from a new customer's first interaction to a fully activated, product-enabled account — spanning identity verification, compliance checks, account creation, product provisioning, and the welcome experience.

Customer onboarding is the first real experience a customer has with a business after signing up. A slow, confusing, or broken onboarding process directly causes abandonment — industry data shows 60–75% of customers abandon onboarding if it takes more than 5 minutes or has unclear steps. A well-designed onboarding system is both a compliance requirement and a revenue driver.

Scale reference: Stripe onboards thousands of businesses per day. Robinhood onboarded 3 million customers in Q1 2021 alone. A mid-tier fintech or SaaS company onboards 1,000–50,000 customers per month. Design for 100,000 customers per month (~3,333/day, ~140/hour average) with peak spikes of 10× during campaigns.

Key unique challenges:

  • Multi-channel entry — customers arrive from web, mobile, referral links, partner APIs, and agent-assisted flows; each channel has different data availability and UX constraints
  • Regulatory compliance — financial services require KYC/AML; healthcare requires HIPAA; regulated industries have mandatory verification steps that cannot be skipped or reordered
  • Partial completion and resumption — most customers do not complete onboarding in one session; the system must save progress and allow seamless resumption days later
  • Progressive profiling — collecting all required information upfront causes abandonment; the system must request only what is needed now and gather the rest over time
  • Downstream provisioning — creating an account triggers provisioning across multiple systems (CRM, billing, product entitlements, communication platforms) that each have their own latency and failure modes

Step 1 — Requirements

Functional Requirements

# Requirement
1 New customer can start registration via web, mobile app, partner API, or agent-assisted portal
2 System collects customer profile information progressively — only required fields at each step
3 System verifies customer identity via KYC with government ID document check
4 System screens customer against AML/sanctions lists (OFAC, PEP lists)
5 Customer verifies email address and phone number via OTP before account activation
6 System creates customer account in the core platform and assigns a unique customer ID
7 System provisions customer in all downstream systems: CRM, billing, product entitlements
8 System enrolls customer in selected products or plans at signup
9 System delivers a personalized welcome journey: welcome email, feature tour, setup checklist
10 Customer can pause and resume onboarding across sessions and devices
11 System tracks onboarding completion percentage and step-by-step funnel analytics
12 Operations team can view onboarding status, abandonment points, and intervene for stuck customers

Non-Functional Requirements

# Requirement
1 Registration form submission responds within 2 seconds (p99)
2 Account activation (KYC cleared + all systems provisioned) completes within 30 seconds for auto-approved flows
3 Zero customer loss — no submitted registration can be silently dropped
4 Onboarding state must be durable — customer can resume after days, weeks, or device changes
5 Downstream provisioning must be idempotent — network retries cannot create duplicate CRM contacts or billing subscriptions
6 GDPR / CCPA compliance — customer can delete their account and all associated data at any point
7 Full audit trail — every step, decision, and verification result permanently logged
8 Support A/B testing — onboarding flow variant assignment tracked per customer
9 High availability — 99.9% uptime for registration and account activation APIs

Out of Scope

  • Post-onboarding customer lifecycle management (upgrades, renewals)
  • Product-specific configuration (handled by each product's setup wizard)
  • Support ticket creation and resolution
  • Billing payment method collection (handled by Billing Service)
  • Agent/rep compensation for assisted onboarding

Step 2 — Capacity Estimation

Traffic Estimates

Monthly registrations:         100,000
Daily registrations:           100K / 30 = ~3,333/day
Average rate:                  3,333 / 86,400 = ~0.04 reg/sec
Peak rate (campaign):          10× = ~0.4 reg/sec = ~24/minute

Onboarding steps per customer (avg):   8 steps
  Email verification:                  1
  Phone verification:                  1
  Profile completion:                  2–3 steps
  Identity verification (KYC):         1
  Plan selection:                      1
  Confirmation:                        1

Step completion events/day:    3,333 × 8 = ~26,664/day = ~0.3 events/sec
Peak (campaign):               ~3 events/sec

Downstream provisioning calls per customer:
  CRM contact:                 1
  Billing subscription:        1
  Product entitlement:         1–3
  Email platform:              1
  Analytics:                   1
  Total:                       ~6 provisioning calls per customer
  Daily provisioning calls:    3,333 × 6 = ~20,000/day

Storage Estimates

Per customer onboarding record:
  Customer profile data:         ~2 KB
  Onboarding step states:        ~500 bytes × 8 steps = 4 KB
  KYC result + documents:        ~20 KB metadata + docs in S3
  Audit events:                  ~200 bytes × 15 events = 3 KB
  Total per customer:            ~9 KB (metadata)

Annual metadata storage:
  100K/month × 9 KB × 12 = ~10.8 GB/year  (trivial)

ID document storage (S3):
  100K/month × 3 images × 500 KB = ~150 GB/month
  1-year retention: ~1.8 TB

Funnel analytics events:
  100K customers × 20 funnel events × 200 bytes = ~400 MB/month

Key Insights

  • Volume is moderate, orchestration complexity is high — 3,333/day is low throughput; challenge is multi-system coordination, not raw scale
  • Partial completion rate is high — industry averages show 40–60% of registrations never complete; system must handle millions of incomplete records efficiently
  • Provisioning is the latency bottleneck — 6 downstream calls during activation; must be parallelized and fault-tolerant
  • Peak campaign load — 10× spikes during promotions; queue-based provisioning absorbs bursts without data loss
  • Document storage dominates — 1.8 TB/year for ID documents; requires object storage with compliance-grade retention

Step 3 — High-Level Architecture

flowchart TD
    Channels["Entry Channels\nWeb / Mobile / Partner API / Agent Portal"]
    AG["API Gateway\nAuth + Rate Limiting + TLS"]
    ORC["Onboarding Orchestrator\n(State machine + step router)"]
    VER["Verification Service\n(Email + Phone OTP)"]
    KYC["KYC/AML Service\n(Identity + sanctions)"]
    PROF["Profile Service\n(Customer data + progressive collection)"]
    PROV["Provisioning Service\n(Downstream system fan-out)"]
    WJ["Welcome Journey Service\n(Email sequences + in-app checklists)"]
    ANL["Analytics Service\n(Funnel + A/B tracking)"]
    AuditS["Audit Service\n(Append-only event log)"]

    CustDB[("PostgreSQL\nCustomer DB")]
    DocStore[("S3\nID Document Store")]
    KF["Kafka\nOnboarding Events"]
    REDIS["Redis\nOTP Store + Step Cache + Idempotency"]

    Channels --> AG
    AG --> ORC
    ORC --> VER
    ORC --> KYC
    ORC --> PROF
    ORC --> PROV
    ORC --> CustDB
    ORC --> REDIS
    ORC --> KF

    KYC --> DocStore
    KF --> WJ
    KF --> ANL
    KF --> AuditS
    PROV --> CRM["CRM\n(Salesforce / HubSpot)"]
    PROV --> BIL["Billing\n(Stripe / Zuora)"]
    PROV --> ENT["Entitlements\n(Product Access)"]
    PROV --> COM["Communication Platform\n(SendGrid / Twilio)"]

Component Responsibilities

Component Responsibility
Onboarding Orchestrator Central state machine — drives customer through steps, persists progress, coordinates all services
Verification Service Email OTP delivery and verification; phone SMS OTP; tracks verification attempts and expiry
KYC/AML Service Government ID document verification; liveness check; OFAC and PEP list screening
Profile Service Stores and validates customer profile data; enforces progressive collection schema
Provisioning Service Fan-out to downstream systems (CRM, billing, entitlements, comms); idempotent; parallel execution
Welcome Journey Service Triggers welcome email sequence; creates in-app onboarding checklist; sends milestone notifications
Analytics Service Records funnel events per step; tracks A/B variant assignments; feeds dashboards
Audit Service Subscribe-only; writes append-only audit records for every state change and decision
Customer DB PostgreSQL — all customer records, onboarding state, verification results, provisioning status
Document Store S3 — uploaded ID documents; encrypted; 1-year minimum retention
Kafka Decouples orchestrator from welcome journey, analytics, and audit
Redis OTP codes (TTL-based); onboarding step cache; provisioning idempotency keys

Step 4 — Onboarding Lifecycle: End-to-End Flow

The complete journey from a new visitor clicking "Sign Up" to a fully activated, provisioned account.

sequenceDiagram
    participant C as Customer
    participant ORC as Onboarding Orchestrator
    participant VER as Verification Service
    participant KYC as KYC/AML Service
    participant PROV as Provisioning Service
    participant DB as Customer DB
    participant KF as Kafka

    C->>ORC: POST /v1/onboarding/start {email, password, channel, referral_code}
    ORC->>DB: INSERT customer {status: REGISTRATION_STARTED, onboarding_id}
    ORC->>VER: SendEmailOTP(email)
    ORC-->>C: 201 {onboarding_id, next_step: "verify_email"}

    C->>ORC: POST /v1/onboarding/{id}/verify-email {otp_code}
    ORC->>VER: VerifyOTP(email, otp_code)
    VER-->>ORC: {verified: true}
    ORC->>DB: UPDATE step: EMAIL_VERIFIED
    ORC-->>C: {next_step: "complete_profile"}

    C->>ORC: POST /v1/onboarding/{id}/profile {name, dob, address, phone}
    ORC->>VER: SendPhoneOTP(phone)
    ORC->>DB: UPDATE step: PROFILE_SUBMITTED
    ORC-->>C: {next_step: "verify_phone"}

    C->>ORC: POST /v1/onboarding/{id}/verify-phone {otp_code}
    ORC->>VER: VerifyOTP(phone, otp_code)
    ORC->>DB: UPDATE step: PHONE_VERIFIED

    Note over ORC,KYC: KYC — may be async or real-time depending on provider
    ORC->>KYC: VerifyIdentity(name, dob, address, id_documents)
    KYC-->>ORC: {status: VERIFIED, kyc_ref_id}
    ORC->>DB: UPDATE step: KYC_PASSED, status: ACCOUNT_ACTIVATING

    ORC->>PROV: ProvisionCustomer(customer_id, plan, channel)
    PROV-->>ORC: {crm_id, billing_id, entitlements: [...], provisioned: true}

    ORC->>DB: UPDATE customer {status: ACTIVE, provisioned: true}
    ORC->>KF: Publish customer.onboarding_completed {customer_id, plan, channel}
    ORC-->>C: {status: "active", account_id, welcome_url}

Step 5 — Onboarding State Machine

The onboarding lifecycle is a directed graph of states. Each state represents where the customer is in the process. The orchestrator enforces valid transitions and persists state durably — so if a customer closes the browser mid-onboarding, they resume exactly where they left off.

stateDiagram-v2
    [*] --> REGISTRATION_STARTED : Customer submits email + password

    REGISTRATION_STARTED --> EMAIL_VERIFICATION_PENDING : OTP sent to email
    REGISTRATION_STARTED --> ABANDONED : No activity for 7 days

    EMAIL_VERIFICATION_PENDING --> EMAIL_VERIFIED : OTP confirmed
    EMAIL_VERIFICATION_PENDING --> EMAIL_VERIFICATION_EXPIRED : OTP expired (no retry within 24h)
    EMAIL_VERIFICATION_PENDING --> REGISTRATION_STARTED : Customer requests new OTP

    EMAIL_VERIFIED --> PROFILE_COLLECTION : Customer proceeds to profile step
    EMAIL_VERIFIED --> ABANDONED : No activity for 7 days

    PROFILE_COLLECTION --> PHONE_VERIFICATION_PENDING : Profile submitted, phone OTP sent
    PHONE_VERIFICATION_PENDING --> PHONE_VERIFIED : OTP confirmed
    PHONE_VERIFIED --> KYC_IN_PROGRESS : KYC check initiated (regulated flows)
    PHONE_VERIFIED --> PLAN_SELECTION : Non-regulated flow — skip KYC

    KYC_IN_PROGRESS --> KYC_PASSED : Identity verified + AML clear
    KYC_IN_PROGRESS --> KYC_FAILED : Identity check failed
    KYC_IN_PROGRESS --> KYC_MANUAL_REVIEW : Inconclusive — human review
    KYC_MANUAL_REVIEW --> KYC_PASSED : Compliance officer approves
    KYC_MANUAL_REVIEW --> KYC_FAILED : Compliance officer rejects

    KYC_FAILED --> DECLINED : Regulatory block — account cannot be created
    KYC_PASSED --> PLAN_SELECTION : Proceed to plan/product selection

    PLAN_SELECTION --> ACCOUNT_ACTIVATING : Plan selected; provisioning initiated

    ACCOUNT_ACTIVATING --> ACTIVE : All downstream systems provisioned
    ACCOUNT_ACTIVATING --> PROVISIONING_PARTIAL : Some systems provisioned; retry in progress
    PROVISIONING_PARTIAL --> ACTIVE : All retries succeeded
    PROVISIONING_PARTIAL --> PROVISIONING_FAILED : Retries exhausted — ops intervention needed

    ACTIVE --> SUSPENDED : Account suspended (policy violation / payment failure)
    ACTIVE --> DELETED : GDPR deletion request fulfilled

    ABANDONED --> REGISTRATION_STARTED : Customer returns via re-engagement email
    DECLINED --> [*]
    DELETED --> [*]

Step Persistence for Resumption

Every step transition is written to the database before the response is sent to the customer. If the service crashes after writing but before responding, the customer retries and the system detects the completed step from the database — not from in-memory state.

-- Atomic step transition — idempotent
INSERT INTO onboarding_steps (onboarding_id, step_name, status, completed_at)
VALUES (:onboarding_id, 'EMAIL_VERIFIED', 'COMPLETED', NOW())
ON CONFLICT (onboarding_id, step_name)
DO UPDATE SET status = EXCLUDED.status, completed_at = EXCLUDED.completed_at
    WHERE onboarding_steps.status != 'COMPLETED';

-- Customer sees their current position on any device
SELECT step_name, status, completed_at
FROM onboarding_steps
WHERE onboarding_id = :id
ORDER BY step_order ASC;

Step 6 — Verification Service

Email Verification

sequenceDiagram
    participant ORC as Orchestrator
    participant VER as Verification Service
    participant REDIS as Redis
    participant NS as Notification Service
    participant C as Customer

    ORC->>VER: SendEmailOTP(onboarding_id, email)
    VER->>VER: Generate 6-digit OTP
    VER->>REDIS: SET otp:email:{onboarding_id} {otp, email} TTL=10min
    VER->>NS: SendEmail(email, subject="Verify your email", body="Your code: {otp}")
    VER-->>ORC: {sent: true, expires_in: 600}

    C->>ORC: POST /verify-email {otp_code: "847291"}
    ORC->>VER: VerifyEmailOTP(onboarding_id, "847291")
    VER->>REDIS: GET otp:email:{onboarding_id}
    REDIS-->>VER: {otp: "847291", email: "[email protected]"}
    VER->>VER: Compare submitted vs stored OTP
    VER->>REDIS: DEL otp:email:{onboarding_id}   (single-use — consume on verify)
    VER-->>ORC: {verified: true}

OTP Security Rules

OTP format:        6-digit numeric (1,000,000 possible values)
TTL:               10 minutes
Max attempts:      5 (then OTP is invalidated; must request new one)
Rate limiting:     Max 3 OTP sends per email per hour
                   Max 10 OTP sends per IP per hour
Single-use:        OTP deleted from Redis on first successful use
Brute force:       After 5 failed attempts → lock OTP + require new one
                   After 10 failed requests in 1 hour → block IP (temporary)

Phone OTP:
  Same rules apply
  Additional: carrier-confirmed delivery check (Twilio delivery webhook)
  Fallback:   Voice call OTP if SMS fails after 2 attempts

For web flows, a magic link (token-based email verification) improves completion rates by eliminating OTP copy-paste:

Magic link:   https://app.example.com/verify?token={signed_jwt}
Token:        JWT signed with server private key
              Payload: {onboarding_id, email, exp: +10min, nonce: uuid}
Single-use:   Token nonce stored in Redis; deleted on use
Expiry:       10 minutes (same as OTP)
Fallback:     If magic link not clicked within 10 min → offer OTP instead

Step 7 — KYC/AML Integration

The KYC/AML step applies only to regulated products (financial accounts, lending, crypto). Non-regulated SaaS products skip directly to account activation. The onboarding orchestrator determines which steps are required based on the product type and jurisdiction.

KYC Flow

sequenceDiagram
    participant ORC as Orchestrator
    participant KYC as KYC/AML Service
    participant IDV as Identity Provider\n(Onfido / Jumio / Persona)
    participant OFAC as OFAC / PEP API
    participant DB as Customer DB

    ORC->>KYC: VerifyIdentity(customer_id, {name, dob, address})
    KYC->>IDV: CreateVerificationSession(customer_id)
    IDV-->>KYC: {session_url: "https://idv.provider.com/session/xyz"}
    KYC-->>ORC: {session_url}
    ORC-->>Customer: Redirect to IDV provider SDK / iframe

    Note over Customer: Customer captures ID document + selfie in IDV flow
    IDV-->>KYC: Webhook: verification_completed\n{customer_id, result: APPROVED, confidence: 0.98}

    KYC->>OFAC: ScreenName(full_name, dob, nationality)
    OFAC-->>KYC: {match: false, score: 0.01}

    KYC->>KYC: Compute final KYC decision
    KYC->>DB: INSERT kyc_result {customer_id, status: VERIFIED, idv_ref, ofac_score}
    KYC-->>ORC: {status: VERIFIED, kyc_result_id}
    ORC->>ORC: Transition customer to KYC_PASSED

Step Skipping by Product Type

Financial account (bank, brokerage, lending):
  Email verification → Phone verification → KYC/AML → Plan selection → Activation

B2B SaaS (non-regulated):
  Email verification → Profile completion → Company verification → Plan selection → Activation

Consumer app (non-regulated):
  Email verification → Profile completion → Activation
  (KYC skipped entirely)

Crypto / Digital assets:
  Email verification → Phone verification → KYC/AML (enhanced) → Accreditation check → Activation

The orchestrator loads a step definition configuration per product type + jurisdiction at startup. Changing the required steps does not require code deployment — only a configuration update.

{
  "product_type": "financial_account",
  "jurisdiction": "US",
  "steps": [
    { "step": "email_verification",  "required": true,  "order": 1 },
    { "step": "profile_collection",  "required": true,  "order": 2 },
    { "step": "phone_verification",  "required": true,  "order": 3 },
    { "step": "kyc_verification",    "required": true,  "order": 4 },
    { "step": "plan_selection",      "required": true,  "order": 5 },
    { "step": "account_activation",  "required": true,  "order": 6 }
  ]
}

Step 8 — Progressive Profiling

Collecting all customer information at registration causes abandonment. Progressive profiling collects the minimum required data at each step and gathers additional information over time — after the customer has already derived value from the product.

Data Collection Tiers

flowchart TB
    T1["Tier 1 — Required at Signup\n(Gate to account creation)\n• Email address\n• Password\n• Full name\n• Date of birth (regulated products)"]

    T2["Tier 2 — Required before first use\n(Gate to product activation)\n• Phone number (for 2FA)\n• Home address\n• ID verification (regulated products)\n• Plan selection"]

    T3["Tier 3 — Collected post-activation\n(Requested progressively)\n• Payment method\n• Company / employer details\n• Tax information (if applicable)\n• Preferences + communication opt-ins"]

    T4["Tier 4 — Optional enrichment\n(Nudged via in-app prompts)\n• Profile photo\n• Job title / industry\n• Use case / goals\n• Referral source"]

    T1 --> T2 --> T3 --> T4

Rule: Never request Tier 3 or Tier 4 data during the initial onboarding flow. The customer has not yet experienced the product and has no reason to trust the platform with additional information.

Profile Completion Scoring

Profile completeness score = weighted sum of completed fields

Field weights:
  Email verified:      20 points  (required)
  Phone verified:      15 points  (required for regulated)
  Full name:           10 points
  Address:             10 points
  ID verified (KYC):   20 points  (required for regulated)
  Payment method:      10 points
  Company info:        5 points
  Profile photo:       5 points
  Preferences set:     5 points

Total possible: 100 points
"Complete" threshold: 70 points
Nudge threshold: > 14 days since signup AND score < 70 → trigger profile completion campaign

Step 9 — Account Provisioning

When a customer completes onboarding, the Provisioning Service fans out to all downstream systems in parallel. Each provisioning call is idempotent — a retry cannot create a duplicate record.

Parallel Provisioning Architecture

sequenceDiagram
    participant ORC as Orchestrator
    participant PROV as Provisioning Service
    participant CRM as CRM (Salesforce)
    participant BIL as Billing (Stripe)
    participant ENT as Entitlements
    participant COM as Email Platform

    ORC->>PROV: ProvisionCustomer(customer_id, plan, profile)

    par Parallel provisioning
        PROV->>CRM: CreateContact(customer_id, name, email, plan)
        PROV->>BIL: CreateCustomer(customer_id, email, plan_id)
        PROV->>ENT: GrantEntitlements(customer_id, plan_features)
        PROV->>COM: CreateContactList(customer_id, email, segments)
    end

    CRM-->>PROV: {crm_id: "00Q5x...", created: true}
    BIL-->>PROV: {stripe_customer_id: "cus_xyz", subscription_id: "sub_abc"}
    ENT-->>PROV: {entitlements: ["feature_a", "feature_b"], granted: true}
    COM-->>PROV: {contact_id: "sg_xyz", lists: ["onboarding", "product_updates"]}

    PROV->>DB: UPDATE customer_provisioning\nSET crm_id, billing_id, entitlements_granted=true,\n    comm_contact_id, status=COMPLETED
    PROV-->>ORC: {all_systems_provisioned: true}

Provisioning Idempotency

Every provisioning call carries an idempotency key derived from the customer ID and system name. If the Provisioning Service crashes mid-fan-out and retries, each downstream system receives the same idempotency key and returns the existing record rather than creating a duplicate.

Idempotency key construction:
  CRM:          provision:crm:{customer_id}
  Billing:      provision:billing:{customer_id}
  Entitlements: provision:entitlements:{customer_id}:{plan_id}
  Comms:        provision:comms:{customer_id}

Storage:        Redis SET NX {key} {result} TTL=7days
                PostgreSQL customer_provisioning table (permanent record)

On idempotency hit:
  → Return cached provisioning result
  → Do not call downstream system again

Partial Provisioning Handling

If some downstream systems fail (e.g., CRM times out but Billing succeeds), the customer is placed in PROVISIONING_PARTIAL state:

flowchart TB
    Result["Provisioning Result"]
    Result --> AllSuccess{"All systems\nprovisioned?"}

    AllSuccess -->|"Yes"| Active["Set status = ACTIVE\nPublish customer.onboarding_completed"]

    AllSuccess -->|"No"| Partial["Set status = PROVISIONING_PARTIAL\nRecord which systems failed\nEnqueue retry job"]

    Retry["Retry Job\n(exponential backoff: 1m → 5m → 15m → 1h)"]
    Retry --> RetryFailed["Re-provision only FAILED systems\n(successful systems already idempotent)"]
    RetryFailed --> AllSuccess

    MaxRetries["Max retries exceeded (4h)\n→ Set status = PROVISIONING_FAILED\n→ Alert ops team\n→ Customer still partially activated"]

Critical design principle: The customer is not blocked during PROVISIONING_PARTIAL. Their account is activated with whatever was successfully provisioned. Missing systems are retried in the background. The customer is unaware of the partial failure — they see a normal "Welcome" screen.


Step 10 — Welcome Journey Orchestration

The welcome journey is the sequence of communications and in-app experiences that guide a new customer through their first week. It is triggered when onboarding completes and orchestrated by the Welcome Journey Service.

Welcome Journey Timeline

flowchart LR
    D0["Day 0 — Activation\n• Welcome email\n• In-app setup checklist\n• Product tour trigger"]

    D1["Day 1\n• Check: has customer logged in?\n  If yes: feature spotlight email\n  If no: re-engagement email"]

    D3["Day 3\n• Check: key action completed?\n  (e.g., first transaction, first project)\n  If yes: congratulations + next step\n  If no: tip email + how-to guide"]

    D7["Day 7\n• Check: profile completeness < 70?\n  If yes: profile completion nudge\n  7-day progress summary email"]

    D14["Day 14\n• Upgrade/upsell email\n  (if on free tier)\n• Check-in survey"]

    D0 --> D1 --> D3 --> D7 --> D14

In-App Onboarding Checklist

The onboarding checklist is a visible, persistent UI element that guides the customer through their first setup actions:

{
  "customer_id":  "cust_abc123",
  "checklist_id": "chk_xyz",
  "progress_pct": 60,
  "items": [
    { "id": "verify_email",   "title": "Verify your email",          "completed": true,  "completed_at": "2025-03-01" },
    { "id": "add_phone",      "title": "Add a phone number",         "completed": true,  "completed_at": "2025-03-01" },
    { "id": "complete_profile","title": "Complete your profile",      "completed": true,  "completed_at": "2025-03-01" },
    { "id": "first_action",   "title": "Complete your first action", "completed": false, "action_url": "/get-started" },
    { "id": "invite_team",    "title": "Invite a team member",       "completed": false, "action_url": "/team/invite" }
  ]
}

Welcome Journey Event Triggers

The Welcome Journey Service subscribes to customer activity events from the product and adjusts the journey in real-time:

Customer Action Journey Adjustment
Logged in within 24h Skip re-engagement email; send feature spotlight instead
Completed first key action Send "Congratulations" email; advance checklist
Invited a team member Mark team item complete; send team onboarding trigger
Upgraded plan Switch to advanced user journey; send premium feature guide
No login in 7 days Trigger re-engagement sequence; alert CSM team
Profile < 70% at Day 14 Trigger profile completion campaign

Step 11 — Abandonment Recovery

60–75% of customers who start onboarding never complete it. Abandonment recovery turns incomplete onboardings into completed accounts.

Abandonment Detection

Definition of abandonment:
  No onboarding activity for:
    EMAIL_VERIFICATION_PENDING:  24 hours
    PROFILE_COLLECTION:          3 days
    KYC_IN_PROGRESS:             7 days
    PLAN_SELECTION:              3 days

Detection job: runs every 30 minutes
  SELECT onboarding_id, current_step, last_activity_at
  FROM onboardings
  WHERE status NOT IN ('ACTIVE', 'DECLINED', 'ABANDONED', 'DELETED')
    AND last_activity_at < NOW() - abandonment_threshold[current_step]

  → Mark as ABANDONED (soft — customer can still resume)
  → Enqueue abandonment recovery sequence

Recovery Sequence

flowchart LR
    Abandoned["Customer Abandoned\nat step: PROFILE_COLLECTION"]

    H1["Hour 1\nEmail: 'Continue where you left off'\n+ direct link to current step"]
    H24["24 Hours\nEmail: 'We saved your progress'\n+ benefit reminder"]
    D3["Day 3\nEmail: 'Still thinking it over?'\n+ social proof / testimonials"]
    D7["Day 7\n'We noticed you haven't completed'\n+ offer: 1-month free trial"]
    D14["Day 14\nFinal email\n'Your account will expire in 7 days'"]
    D21["Day 21\nSoft delete — mark as EXPIRED\nData retained 30 days then purged"]

    Abandoned --> H1 --> H24 --> D3 --> D7 --> D14 --> D21

    Resume["Customer clicks link\n→ Resumes exactly at current step\n→ Recovery sequence cancelled"]
    H1 --> Resume
    H24 --> Resume
    D3 --> Resume

Step 12 — Database Design

Core Tables

-- Master onboarding record
CREATE TABLE onboardings (
    onboarding_id    UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    customer_id      UUID,                     -- NULL until account is created
    email            VARCHAR(255)  NOT NULL,
    channel          VARCHAR(20)   NOT NULL,   -- 'WEB' | 'MOBILE' | 'PARTNER_API' | 'AGENT'
    product_type     VARCHAR(30)   NOT NULL,
    jurisdiction     CHAR(2)       NOT NULL DEFAULT 'US',
    status           VARCHAR(30)   NOT NULL DEFAULT 'REGISTRATION_STARTED',
    current_step     VARCHAR(50)   NOT NULL DEFAULT 'EMAIL_VERIFICATION',
    ab_variant       VARCHAR(20),              -- A/B test variant assigned at start
    referral_code    VARCHAR(50),
    utm_source       VARCHAR(100),
    idempotency_key  VARCHAR(255)  UNIQUE,
    last_activity_at TIMESTAMPTZ   NOT NULL DEFAULT NOW(),
    created_at       TIMESTAMPTZ   NOT NULL DEFAULT NOW(),
    updated_at       TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

-- Step-level completion tracking
CREATE TABLE onboarding_steps (
    step_id          UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    onboarding_id    UUID          NOT NULL REFERENCES onboardings(onboarding_id),
    step_name        VARCHAR(50)   NOT NULL,
    step_order       INT           NOT NULL,
    status           VARCHAR(20)   NOT NULL DEFAULT 'PENDING',  -- PENDING | IN_PROGRESS | COMPLETED | SKIPPED | FAILED
    started_at       TIMESTAMPTZ,
    completed_at     TIMESTAMPTZ,
    attempts         INT           NOT NULL DEFAULT 0,
    metadata         JSONB,                    -- step-specific data (OTP attempt count, KYC ref, etc.)
    UNIQUE (onboarding_id, step_name)
);

-- Customer master record (created at activation)
CREATE TABLE customers (
    customer_id      UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    onboarding_id    UUID          NOT NULL REFERENCES onboardings(onboarding_id),
    email            VARCHAR(255)  NOT NULL UNIQUE,
    phone            VARCHAR(20),
    full_name        VARCHAR(200)  NOT NULL,
    date_of_birth    DATE,
    status           VARCHAR(20)   NOT NULL DEFAULT 'ACTIVE',
    kyc_status       VARCHAR(20)   NOT NULL DEFAULT 'NOT_REQUIRED',
    profile_score    INT           NOT NULL DEFAULT 0,
    plan_id          VARCHAR(50),
    created_at       TIMESTAMPTZ   NOT NULL DEFAULT NOW(),
    updated_at       TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

-- Downstream provisioning status
CREATE TABLE customer_provisioning (
    provisioning_id  UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    customer_id      UUID          NOT NULL REFERENCES customers(customer_id),
    system_name      VARCHAR(30)   NOT NULL,   -- 'CRM' | 'BILLING' | 'ENTITLEMENTS' | 'COMMS'
    status           VARCHAR(20)   NOT NULL DEFAULT 'PENDING',
    external_id      VARCHAR(200),             -- ID in the downstream system
    idempotency_key  VARCHAR(255)  NOT NULL UNIQUE,
    attempt_count    INT           NOT NULL DEFAULT 0,
    last_attempted_at TIMESTAMPTZ,
    provisioned_at   TIMESTAMPTZ,
    error_message    TEXT,
    created_at       TIMESTAMPTZ   NOT NULL DEFAULT NOW(),
    UNIQUE (customer_id, system_name)
);

-- Immutable onboarding event log
CREATE TABLE onboarding_events (
    event_id         UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    onboarding_id    UUID          NOT NULL REFERENCES onboardings(onboarding_id),
    event_type       VARCHAR(50)   NOT NULL,
    from_status      VARCHAR(30),
    to_status        VARCHAR(30),
    step_name        VARCHAR(50),
    actor_type       VARCHAR(20)   NOT NULL DEFAULT 'SYSTEM',
    actor_id         VARCHAR(100),
    metadata         JSONB,
    occurred_at      TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

-- In-app onboarding checklist
CREATE TABLE onboarding_checklists (
    checklist_id     UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    customer_id      UUID          NOT NULL REFERENCES customers(customer_id) UNIQUE,
    total_items      INT           NOT NULL,
    completed_items  INT           NOT NULL DEFAULT 0,
    progress_pct     INT           GENERATED ALWAYS AS
                       (CASE WHEN total_items = 0 THEN 0
                             ELSE (completed_items * 100 / total_items) END) STORED,
    completed_at     TIMESTAMPTZ,
    created_at       TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

CREATE TABLE checklist_items (
    item_id          UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    checklist_id     UUID          NOT NULL REFERENCES onboarding_checklists(checklist_id),
    item_key         VARCHAR(50)   NOT NULL,
    title            VARCHAR(200)  NOT NULL,
    action_url       VARCHAR(500),
    completed        BOOLEAN       NOT NULL DEFAULT FALSE,
    completed_at     TIMESTAMPTZ,
    UNIQUE (checklist_id, item_key)
);

Key Indexes

-- Lookup onboarding by email (resumption flow)
CREATE INDEX idx_onboarding_email      ON onboardings(email, status);

-- Active onboardings by step (queue monitoring)
CREATE INDEX idx_onboarding_step       ON onboardings(current_step, status)
    WHERE status NOT IN ('ACTIVE', 'DECLINED', 'ABANDONED', 'DELETED');

-- Abandonment detection scan
CREATE INDEX idx_onboarding_activity   ON onboardings(last_activity_at, status)
    WHERE status NOT IN ('ACTIVE', 'DECLINED', 'ABANDONED', 'DELETED');

-- Provisioning retry queue
CREATE INDEX idx_provisioning_retry    ON customer_provisioning(status, last_attempted_at)
    WHERE status IN ('PENDING', 'FAILED');

-- Events per onboarding (timeline)
CREATE INDEX idx_events_onboarding     ON onboarding_events(onboarding_id, occurred_at DESC);

-- Steps by onboarding (resumption — load current position)
CREATE INDEX idx_steps_onboarding      ON onboarding_steps(onboarding_id, step_order);

Step 13 — API Design

Start Onboarding

POST /v1/onboarding/start

{
  "idempotency_key": "reg-device-D123-2025-03-01T10:00:00Z",
  "email":           "[email protected]",
  "password":        "••••••••",
  "channel":         "web",
  "product_type":    "financial_account",
  "referral_code":   "FRIEND2025",
  "utm_source":      "google_ads"
}

201 Created:

{
  "onboarding_id":   "onb_9f8e7d6c",
  "status":          "email_verification_pending",
  "next_step": {
    "step":          "verify_email",
    "instruction":   "Enter the 6-digit code sent to [email protected]",
    "expires_in":    600
  },
  "created_at":      "2025-03-01T10:00:02Z"
}

Resume Onboarding

GET /v1/onboarding/{onboarding_id}/status

{
  "onboarding_id":   "onb_9f8e7d6c",
  "status":          "profile_collection",
  "progress_pct":    37,
  "current_step": {
    "step":          "complete_profile",
    "fields_required": ["phone", "date_of_birth", "home_address"],
    "fields_optional": ["job_title"]
  },
  "completed_steps": ["email_verification", "password_set"],
  "remaining_steps": ["complete_profile", "verify_phone", "kyc_verification", "plan_selection"]
}

Complete a Step

POST /v1/onboarding/{onboarding_id}/steps/{step_name}

{
  "step_name": "profile_collection",
  "data": {
    "full_name":   "Alex Johnson",
    "date_of_birth": "1990-05-15",
    "phone":       "+14155550100",
    "address": {
      "line1": "123 Main St", "city": "San Francisco",
      "state": "CA", "zip": "94105", "country": "US"
    }
  }
}

200 OK:

{
  "step_name":   "profile_collection",
  "status":      "completed",
  "next_step": {
    "step":      "verify_phone",
    "instruction": "Enter the 6-digit code sent to +1 (415) 555-0100"
  },
  "progress_pct": 62
}

Activate Account (Final Step)

POST /v1/onboarding/{onboarding_id}/activate

{
  "plan_id":     "plan_starter",
  "accept_terms": true,
  "accepted_at": "2025-03-01T10:05:00Z"
}

200 OK:

{
  "customer_id":   "cust_abc123",
  "status":        "active",
  "account_url":   "https://app.example.com/dashboard",
  "welcome_message": "Welcome to Example! Your account is ready.",
  "checklist_url": "https://app.example.com/setup",
  "activated_at":  "2025-03-01T10:05:12Z"
}

Step 14 — Kafka Event Architecture

flowchart TB
    ORC["Onboarding Orchestrator"]

    ORC --> E1["onboarding.started"]
    ORC --> E2["onboarding.step_completed"]
    ORC --> E3["onboarding.kyc_completed"]
    ORC --> E4["onboarding.abandoned"]
    ORC --> E5["onboarding.completed"]
    ORC --> E6["customer.provisioned"]

    E1 --> ANL["Analytics\n(funnel tracking)"]
    E1 --> AuditS["Audit Service"]

    E2 --> ANL
    E2 --> AuditS

    E3 --> ANL
    E3 --> AuditS

    E4 --> WJ["Welcome Journey\n(abandonment recovery sequence)"]
    E4 --> ANL

    E5 --> WJ2["Welcome Journey\n(welcome email + checklist)"]
    E5 --> ANL
    E5 --> AuditS
    E5 --> PROV["Provisioning Service\n(fan-out to downstream systems)"]

    E6 --> NS["Notification\n('Your account is ready')"]
    E6 --> ANL

Step 15 — Observability and Failure Handling

Funnel Analytics Dashboard

The most important visibility tool for onboarding is the step-by-step conversion funnel. Every step drop-off is an opportunity to improve:

Step Entered Completed Drop-off Rate Avg Time
Registration start 100,000 100,000 0%
Email verification 100,000 87,200 12.8% ⚠️ 3.2 min
Profile collection 87,200 71,300 18.2% ⚠️ 4.8 min
Phone verification 71,300 68,400 4.1% 2.1 min
KYC verification 68,400 59,600 12.9% ⚠️ 8.4 min
Plan selection 59,600 55,100 7.5% 2.6 min
Activation 55,100 55,100
Overall conversion 100,000 55,100 44.9% 21 min avg

High drop-off at email verification (12.8%) signals OTP delivery problems or too-short expiry. High drop-off at profile collection (18.2%) signals too many required fields. High drop-off at KYC (12.9%) signals friction in the document capture flow.

Key Metrics

Metric Description Alert Threshold
onboarding.registration_success_rate % registrations that start successfully < 99%
onboarding.overall_completion_rate % started → activated < 50% (structural UX issue)
onboarding.step_drop_off_rate Per-step abandonment rate > 20% for any single step
onboarding.avg_time_to_activation_min Mean time from start to ACTIVE > 30 minutes
onboarding.kyc_auto_approval_rate % KYC auto-approved without manual review < 85% (IDV provider issue)
provisioning.failure_rate % provisioning calls failing > 2%
provisioning.partial_rate % customers stuck in PROVISIONING_PARTIAL > 1h > 1%
otp.delivery_success_rate % OTPs delivered successfully < 98%
onboarding.abandonment_recovery_rate % abandoned customers who return Track weekly
kafka.consumer_lag Lag on onboarding event topics > 5,000 messages

CAT Event Handling (Viral Campaign Spikes)

A promotional campaign or viral moment can drive 10× normal registration volume in minutes:

flowchart LR
    Spike["10× Registration Spike\n(campaign launch or viral post)"]

    Spike --> KF["Kafka\nRegistration topic\n(absorbs burst — never drops)"]
    KF --> Workers["Onboarding Workers\n(auto-scale on consumer lag)"]
    Workers --> DB["PostgreSQL\n(all registrations persisted)"]

    Spike --> KYC_Queue["KYC Queue\n(IDV provider has rate limits)\n→ Queue KYC requests\n→ Process at IDV rate limit"]

    Spike --> PROV_Queue["Provisioning Queue\n(CRM/billing rate limits)\n→ Batch provisioning\n→ Customer sees 'activating' screen"]

During spikes, the customer's onboarding experience remains responsive (registration completes instantly) while KYC and provisioning process from a queue. The customer sees intermediate status pages ("We're setting up your account") rather than slow responses.


Design Trade-offs

Trade-off 1: Monolithic Onboarding Flow vs Step-by-Step API

Approach Pros Cons
Step-by-step API (chosen) — each step is a separate API call Resumable across sessions and devices; step state is durable; fine-grained analytics per step More round trips; more complex state management
Single form submission One API call; simpler frontend No resumption; entire form lost on failure or abandonment; no step-level analytics

Decision: Step-by-step API. The #1 cause of onboarding abandonment is users unable to resume after an interruption. A monolithic form is unsalvageable if the customer gets a phone call mid-fill. Step-by-step state persistence is the foundation of a high-converting onboarding flow.

Trade-off 2: Synchronous vs Asynchronous Provisioning

Approach Pros Cons
Asynchronous provisioning (chosen) Customer sees "Welcome" immediately; provisioning failures don't block activation Customer may try to use a feature before all systems are provisioned
Synchronous provisioning Customer is guaranteed fully provisioned before seeing welcome screen Adds 2–10 seconds to activation; any downstream timeout blocks activation entirely

Decision: Asynchronous with graceful degradation. The customer is activated immediately and shown the welcome screen. If a downstream system is not yet provisioned, the feature shows a loading state or graceful "coming soon" message. Background retries complete provisioning within minutes. A synchronous approach would make the activation time dependent on the slowest downstream system.

Trade-off 3: Minimal Upfront Data vs Complete Profile at Signup

Approach Pros Cons
Progressive profiling (chosen) Higher conversion; lower abandonment; trust built before data requested Profile may be incomplete at activation; some features gated until profile complete
Complete profile at signup Customer fully profiled from day one; no follow-up collection needed High abandonment; customers don't trust new platforms with all data upfront

Decision: Progressive profiling always. Industry data consistently shows shorter signup forms double conversion rates. Collecting additional data over time, after the customer has derived value, is far more effective than collecting it all upfront.

Trade-off 4: Single Welcome Email vs Journey Orchestration

Approach Pros Cons
Journey orchestration (chosen) Behavior-based; adapts to what the customer actually does; higher engagement More complex; requires activity event tracking; needs a journey orchestration system
Single welcome email Simple; no infrastructure needed One-size-fits-all; no adaptation to customer behavior; lower activation rate

Decision: Journey orchestration. A single welcome email is a commodity. A behavior-adaptive welcome journey that nudges the customer based on what they have and haven't done produces measurably higher activation, retention, and first-week engagement. The infrastructure cost is justified by the conversion impact.


Common Interview Mistakes

Mistake Why It Matters
Making onboarding a single synchronous API call No resumption on failure; entire flow lost if any step fails; no step-level analytics
Not persisting step state to the database Onboarding state in memory is lost on server restart; customer must restart from beginning
Synchronous provisioning blocking activation If Salesforce is down, no customers can activate — downstream dependency becomes a hard blocker
Missing idempotency on provisioning calls Retry after a provisioning timeout creates duplicate CRM contacts, duplicate billing subscriptions
Collecting all profile data at signup Industry data shows this is the #1 cause of abandonment; progressive profiling is not optional for high conversion
No abandonment recovery flow 60%+ of customers will abandon; without recovery, that's permanent revenue loss
Ignoring KYC step skipping by product type Applying KYC to products that don't require it (B2B SaaS, consumer apps) creates unnecessary friction and drop-off
No funnel analytics per step Without step-level drop-off data, product teams cannot identify where to improve the flow

Summary

The Customer Onboarding System is one of the highest-leverage systems in any customer-facing business. It is the first real product experience after signup, the foundation for regulatory compliance, and the primary driver of early customer activation.

flowchart TB
    Core["Customer Onboarding System"]

    Core --> StateMachine["Durable State Machine\nStep persistence\nCross-session resumption"]
    Core --> Verification["Verification Service\nEmail + Phone OTP\nMagic link option"]
    Core --> KYC_Block["KYC/AML\nProduct-type gated\nAsync IDV webhook"]
    Core --> Progressive["Progressive Profiling\nMinimal upfront data\nTiered collection"]
    Core --> Provisioning["Parallel Provisioning\nIdempotent fan-out\nPartial failure tolerance"]
    Core --> Journey["Welcome Journey\nBehavior-adaptive\nAbandonment recovery"]

The four design principles that make onboarding convert:

  1. State is durable, not in-memory — every step completion is written to the database before the response is sent. A customer who closes their browser mid-onboarding resumes exactly where they left off, on any device, days later.

  2. Provisioning never blocks activation — the customer sees a welcome screen the moment their account is created. Downstream system provisioning happens asynchronously in the background with idempotent retries. The customer experience is decoupled from infrastructure reliability.

  3. Ask only what is needed now — progressive profiling is not a compromise; it is the scientifically validated approach to maximizing completion rates. Every additional required field at signup costs conversion.

  4. Measure every step — without step-level funnel analytics, product teams operate blind. The onboarding funnel dashboard is the primary diagnostic tool for identifying where customers are lost and why.