Domain Specification: Customer Onboarding
Validated against PRD v1.0 — All FR-ON-* requirements implemented here. See Traceability Matrix.
1. Context Overview
Bounded Context: onboarding
Responsibility: Manage the customer onboarding lifecycle — from application intake to final acceptance decision.
Owns: Customer, Individual, LegalEntity, OwnershipRelationship, Document, ApplicationIntake, OnboardingDecision aggregates.
Depends On: Configuration Engine (workflow templates, document rules), Workflow Orchestration (state machine driver), Screening (invoked at screening step), Risk Rating (invoked at risk step), Network Analysis (invoked for ownership mapping).
Events Published: CustomerCreated, DocumentUploaded, IdentityVerified, UBOIdentified, OnboardingDecisionMade.
2. State Machine
stateDiagram-v2
[*] --> NEW
NEW --> INTAKE : application submitted
INTAKE --> DATA_COLLECTION : classification complete
DATA_COLLECTION --> DOCUMENT_COLLECTION : data captured
DOCUMENT_COLLECTION --> VALIDATION_PENDING : mandatory docs received
VALIDATION_PENDING --> ON_HOLD : missing info
state VALIDATION_PENDING {
[*] --> identity_validated
identity_validated --> SCREENING_PENDING
identity_validated --> RISK_ASSESSMENT_PENDING
identity_validated --> NETWORK_ANALYSIS_PENDING
}
SCREENING_PENDING --> ANALYST_REVIEW : screening result
RISK_ASSESSMENT_PENDING --> ANALYST_REVIEW : risk assessment
NETWORK_ANALYSIS_PENDING --> ANALYST_REVIEW : network analysis
ANALYST_REVIEW --> WAITING_EXTERNAL : additional docs requested
ANALYST_REVIEW --> APPROVED : LOW risk fast-track
ANALYST_REVIEW --> APPROVED : MEDIUM standard review
ANALYST_REVIEW --> REJECTED : MEDIUM standard review
ANALYST_REVIEW --> EDD_REVIEW : HIGH risk
EDD_REVIEW --> COMPLIANCE_APPROVAL : EDD report complete
COMPLIANCE_APPROVAL --> APPROVED : FCC approves
COMPLIANCE_APPROVAL --> REJECTED : FCC rejects
COMPLIANCE_APPROVAL --> POLICY_EXCEPTION : policy override
APPROVED --> CLOSED
REJECTED --> CLOSED
WITHDRAWN --> CLOSED
State Transition Rules
| From |
To |
Trigger |
Actor |
| NEW |
INTAKE |
Application submitted |
Relationship Manager |
| INTAKE |
DATA_COLLECTION |
Classification complete |
Onboarding Specialist |
| DATA_COLLECTION |
DOCUMENT_COLLECTION |
All required data fields captured |
System |
| DOCUMENT_COLLECTION |
VALIDATION_PENDING |
Mandatory documents received |
System (minimum docs check) |
| VALIDATION_PENDING |
SCREENING_PENDING |
Identity validated |
System |
| VALIDATION_PENDING |
RISK_ASSESSMENT_PENDING |
Identity validated |
System |
| VALIDATION_PENDING |
NETWORK_ANALYSIS_PENDING |
Identity validated (conditional: corporate only) |
System |
| SCREENING_PENDING |
ANALYST_REVIEW |
Screening result received |
System |
| RISK_ASSESSMENT_PENDING |
ANALYST_REVIEW |
Risk assessment received |
System |
| NETWORK_ANALYSIS_PENDING |
ANALYST_REVIEW |
Network analysis received |
System |
| ANALYST_REVIEW |
APPROVED |
Analyst approves low/medium risk |
KYC Analyst |
| ANALYST_REVIEW |
EDD_REVIEW |
High risk → escalate to EDD |
System (auto-branch) |
| ANALYST_REVIEW |
WAITING_EXTERNAL |
Additional documents requested |
KYC Analyst |
| EDD_REVIEW |
COMPLIANCE_APPROVAL |
EDD report complete |
EDD Analyst |
| COMPLIANCE_APPROVAL |
APPROVED |
FCC approves |
FCC Reviewer |
| COMPLIANCE_APPROVAL |
REJECTED |
FCC rejects |
FCC Reviewer |
| COMPLIANCE_APPROVAL |
POLICY_EXCEPTION |
Policy override required |
FCC Reviewer |
| Any active |
ON_HOLD |
Manual hold |
Supervisor |
| Any active |
PROHIBITED |
Sanctions confirmed hit |
System (auto-block) |
| WAITING_EXTERNAL |
ANALYST_REVIEW |
Documents received |
System |
Guard: Invalid transitions blocked and logged. Transitions only allowed from states listed in From column.
3. Data Model
3.1 Customer (Aggregate Root)
data class Customer(
val customerId: UUID,
val customerType: CustomerType, // INDIVIDUAL, LEGAL_ENTITY
val status: CustomerStatus, // ACTIVE, SUSPENDED, CLOSED, PROHIBITED
val riskBand: RiskBand?, // LOW, MEDIUM, HIGH (updated by risk rating)
val createdAt: Instant,
val updatedAt: Instant,
// Related entities loaded on demand (not part of aggregate root):
// individual: Individual?
// legalEntity: LegalEntity?
// addresses: List<Address>
// ownershipRelationships: List<OwnershipRelationship>
// documents: List<Document>
)
enum class CustomerType { INDIVIDUAL, LEGAL_ENTITY }
enum class CustomerStatus { ACTIVE, SUSPENDED, CLOSED, PROHIBITED, WITHDRAWN }
3.2 Individual
data class Individual(
val individualId: UUID, // = customerId (1:1)
val firstName: String,
val lastName: String,
val aliases: List<String>,
val dateOfBirth: LocalDate,
val nationality: Country,
val residenceCountry: Country,
val taxResidency: Country?,
val occupation: String?,
val pepFlag: Boolean,
val pepLevel: PepLevel?, // NATIONAL, INTERNATIONAL, CLOSE_ASSOCIATE
val sourceOfWealth: String?, // encrypted at rest
val sourceOfFunds: String? // encrypted at rest
)
3.3 LegalEntity
data class LegalEntity(
val entityId: UUID, // = customerId (1:1)
val legalName: String,
val tradeName: String?,
val registrationNumber: String,
val incorporationCountry: Country,
val legalForm: LegalForm, // LLC, PLC, LTD, PARTNERSHIP, etc.
val industryCode: String?,
val incorporationDate: LocalDate
)
3.4 OwnershipRelationship
data class OwnershipRelationship(
val relationshipId: UUID,
val parentEntityId: UUID, // owner
val childEntityId: UUID, // owned
val ownershipPercentage: BigDecimal, // 0 < x <= 100
val controlType: ControlType, // DIRECT, INDIRECT, BENEFICIAL
val effectiveFrom: LocalDate,
val effectiveTo: LocalDate?, // null = current
val confidenceScore: Double // 0.0 - 1.0
)
3.5 Document
data class Document(
val documentId: UUID,
val customerId: UUID,
val documentType: DocumentType,
val fileName: String,
val content: ByteArray, // stored in DB BYTEA, encrypted at rest
val contentHash: String, // SHA-256
val issueDate: LocalDate?,
val expiryDate: LocalDate?,
val validationStatus: ValidationStatus, // PENDING, VERIFIED, EXPIRED, REJECTED
val validatedBy: UUID?,
val validatedAt: Instant?,
val uploadedAt: Instant
)
enum class DocumentType {
PASSPORT, DRIVERS_LICENSE, NATIONAL_ID,
INCORPORATION_CERTIFICATE, CHAMBER_REGISTRATION,
SHAREHOLDER_REGISTER, DIRECTOR_IDENTIFICATION,
UBO_DECLARATION, PROOF_OF_ADDRESS,
SOURCE_OF_WEALTH, SOURCE_OF_FUNDS,
BUSINESS_LICENSE, TAX_REGISTRATION
}
enum class ValidationStatus { PENDING, VERIFIED, EXPIRED, REJECTED }
3.6 ApplicationIntake
data class ApplicationIntake(
val intakeId: UUID,
val customerId: UUID,
val submittedBy: UUID, // Relationship Manager
val customerType: CustomerType,
val jurisdiction: Country,
val businessLine: String,
val productInterest: String,
val expectedMonthlyVolume: String?, // LOW, MEDIUM, HIGH
val expectedAnnualTurnover: String?,
val notes: String?,
val classification: CustomerArchetype?,
val workflowTemplateId: String?, // resolved after classification
val submittedAt: Instant
)
enum class CustomerArchetype {
RETAIL_INDIVIDUAL, SME, CORPORATE, CORRESPONDENT_BANKING,
PRIVATE_BANKING, LEASING, SPECIALIZED
}
3.7 OnboardingDecision
data class OnboardingDecision(
val decisionId: UUID,
val customerId: UUID,
val decisionType: DecisionType,
val actorType: ActorType, // USER, SYSTEM
val actorId: UUID?,
val rationale: String, // mandatory, cannot be empty
val restrictions: String?, // only when APPROVED_WITH_RESTRICTIONS
val evidenceRefs: List<UUID>, // document IDs supporting this decision
val configVersion: String, // config version active when decision made
val madeAt: Instant
)
enum class DecisionType {
APPROVED, APPROVED_WITH_RESTRICTIONS, APPROVED_PENDING_EDD,
REJECTED, WITHDRAWN, PROHIBITED
}
4. API Contracts
4.1 Submit Application
POST /api/v1/onboarding/applications
Authorization: Bearer <jwt>
Idempotency-Key: <uuid>
Request:
{
"customerType": "LEGAL_ENTITY",
"legalName": "ACME Holdings B.V.",
"registrationNumber": "12345678",
"incorporationCountry": "NLD",
"jurisdiction": "NLD",
"businessLine": "COMMERCIAL_LENDING",
"productInterest": "TERM_LOAN",
"expectedMonthlyVolume": "MEDIUM",
"notes": "Existing relationship with subsidiary ACME Trading"
}
Response 201:
{
"applicationId": "uuid",
"customerId": "uuid",
"status": "INTAKE",
"classification": null,
"nextAction": "Awaiting intake review by Onboarding Specialist"
}
4.2 Classify Customer
POST /api/v1/onboarding/applications/{applicationId}/classify
Authorization: Bearer <jwt>
Request body optional — system auto-classifies based on intake data. Specialist can override.
Response 200:
{
"applicationId": "uuid",
"classification": "CORPORATE",
"workflowTemplateId": "Corporate_NLD_Lending_Onboarding_v1",
"requiredDocuments": [
{ "type": "INCORPORATION_CERTIFICATE", "mandatory": true },
{ "type": "CHAMBER_REGISTRATION", "mandatory": true },
{ "type": "SHAREHOLDER_REGISTER", "mandatory": true },
{ "type": "DIRECTOR_IDENTIFICATION", "mandatory": true },
{ "type": "UBO_DECLARATION", "mandatory": true }
],
"state": "DATA_COLLECTION"
}
4.3 Upload Document
POST /api/v1/onboarding/customers/{customerId}/documents
Content-Type: multipart/form-data
Form fields: documentType, issueDate (optional), expiryDate (optional)
File: <binary>
Response 201:
{
"documentId": "uuid",
"documentType": "INCORPORATION_CERTIFICATE",
"validationStatus": "PENDING",
"uploadedAt": "2025-06-01T10:30:00Z"
}
4.4 Validate Document
POST /api/v1/onboarding/documents/{documentId}/validate
Authorization: Bearer <jwt>
Request:
{
"status": "VERIFIED",
"notes": "Incorporation certificate matches Chamber of Commerce registry"
}
Response 200:
{
"documentId": "uuid",
"validationStatus": "VERIFIED",
"validatedBy": "user-uuid",
"validatedAt": "2025-06-01T10:35:00Z"
}
4.5 Add Ownership
POST /api/v1/onboarding/customers/{customerId}/ownership
Authorization: Bearer <jwt>
Request:
{
"parentEntityId": "ubo-customer-uuid",
"ownershipPercentage": 45.0,
"controlType": "DIRECT",
"effectiveFrom": "2020-01-01",
"confidenceScore": 0.95
}
Response 201: { "relationshipId": "uuid" }
4.6 Get UBOs
GET /api/v1/onboarding/customers/{customerId}/ubos?threshold=25
Authorization: Bearer <jwt>
Response 200:
{
"customerId": "uuid",
"uboThreshold": 25.0,
"ubos": [
{
"entityId": "uuid",
"name": "John Smith",
"ownershipPercentage": 45.0,
"chain": ["ACME Holdings B.V.", "ACME International Ltd.", "John Smith"],
"depth": 2
}
],
"totalDeclared": 100.0,
"unidentifiedGap": 0.0
}
4.7 Make Decision
POST /api/v1/onboarding/customers/{customerId}/decisions
Authorization: Bearer <jwt>
Idempotency-Key: <uuid>
Request:
{
"decisionType": "APPROVED_WITH_RESTRICTIONS",
"rationale": "Customer represents moderate risk due to cross-border ownership. Approved subject to enhanced transaction monitoring for first 6 months.",
"restrictions": "Enhanced transaction monitoring for 6 months. Monthly review of transaction patterns.",
"evidenceRefs": ["doc-uuid-1", "doc-uuid-2"]
}
Response 201:
{
"decisionId": "uuid",
"customerId": "uuid",
"decisionType": "APPROVED_WITH_RESTRICTIONS",
"state": "CLOSED",
"madeAt": "2025-06-01T14:00:00Z"
}
5. Workflow Templates
{
"templateId": "Corporate_NLD_Lending_Onboarding_v1",
"customerArchetype": "CORPORATE",
"jurisdiction": "NLD",
"businessLine": "COMMERCIAL_LENDING",
"states": [
{ "name": "NEW", "initial": true },
{ "name": "INTAKE" },
{ "name": "DATA_COLLECTION" },
{ "name": "DOCUMENT_COLLECTION" },
{ "name": "VALIDATION_PENDING" },
{ "name": "SCREENING_PENDING" },
{ "name": "RISK_ASSESSMENT_PENDING" },
{ "name": "NETWORK_ANALYSIS_PENDING" },
{ "name": "ANALYST_REVIEW" },
{ "name": "EDD_REVIEW" },
{ "name": "COMPLIANCE_APPROVAL" },
{ "name": "APPROVED" },
{ "name": "REJECTED" },
{ "name": "CLOSED" },
{ "name": "ON_HOLD" },
{ "name": "WAITING_EXTERNAL" }
],
"transitions": [ /* ... see state machine above ... */ ],
"parallelTasks": {
"after": "VALIDATION_PENDING",
"tasks": [
{ "type": "SCREENING", "module": "name_screening" },
{ "type": "RISK_RATING", "module": "customer_risk_rating" },
{ "type": "NETWORK_ANALYSIS", "module": "network_analysis" }
],
"waitFor": "ALL"
},
"humanTasks": [
{
"state": "ANALYST_REVIEW",
"taskType": "ONBOARDING_REVIEW",
"assignedRole": "KYC_ANALYST",
"slaHours": 24,
"form": {
"actions": ["APPROVE", "REJECT", "REQUEST_INFO", "ESCALATE"],
"requiredFields": ["rationale"]
}
},
{
"state": "EDD_REVIEW",
"taskType": "EDD_DEEP_DIVE",
"assignedRole": "EDD_ANALYST",
"slaHours": 48,
"form": {
"actions": ["SUBMIT_REPORT", "REQUEST_INFO"],
"requiredFields": ["eddReport", "recommendation"]
}
},
{
"state": "COMPLIANCE_APPROVAL",
"taskType": "COMPLIANCE_DECISION",
"assignedRole": "FCC_REVIEWER",
"slaHours": 24,
"form": {
"actions": ["APPROVE", "REJECT", "OVERRIDE"],
"requiredFields": ["rationale"]
}
}
],
"conditionalBranches": [
{
"state": "ANALYST_REVIEW",
"condition": "riskBand == 'HIGH'",
"targetState": "EDD_REVIEW"
},
{
"state": "ANALYST_REVIEW",
"condition": "riskBand == 'MEDIUM' && screeningStatus == 'NO_MATCH'",
"targetState": "APPROVED"
}
],
"slaTimers": {
"INTAKE": { "hours": 4 },
"DATA_COLLECTION": { "hours": 8 },
"DOCUMENT_COLLECTION": { "hours": 24, "pauseOnState": "WAITING_EXTERNAL" },
"VALIDATION_PENDING": { "hours": 24 },
"SCREENING_PENDING": { "hours": 1 },
"RISK_ASSESSMENT_PENDING": { "hours": 1 },
"ANALYST_REVIEW": { "hours": 24 },
"EDD_REVIEW": { "hours": 48 },
"COMPLIANCE_APPROVAL": { "hours": 24 }
}
}
6. Document Requirements (Configurable)
Default Rules by Archetype
| Archetype |
Mandatory Documents |
Optional |
| Retail Individual |
Passport/ID, Proof of Address |
— |
| SME |
Chamber Registration, Director ID, UBO Declaration |
Shareholder Register |
| Corporate |
Incorporation Cert, Chamber Registration, Shareholder Register, Director IDs (all), UBO Declaration |
Business License |
| Correspondent Banking |
All Corporate docs + Source of Wealth, Regulatory License |
— |
| Private Banking |
Source of Wealth, Source of Funds, Tax Residency Proof |
— |
Validation Rules
- Passport/ID: Must not be expired. Issue date ≤ today. Expiry date ≥ today + 30 days (grace period configurable).
- Incorporation Certificate: Registration number must match corporate registry lookup. Incorporation date ≤ today.
- Proof of Address: Must be ≤ 3 months old (configurable). Address must match declared residence/registered address.
- UBO Declaration: Must list all individuals with ≥ 25% ownership (threshold configurable). Total declared ownership must = 100% ± 5%.
7. Escalation Rules
| Trigger |
Level |
Route To |
Auto Action |
| Document missing > SLA |
L2 |
Supervisor |
Reminder escalated |
| Identity mismatch |
L2 |
Senior KYC Analyst |
Task re-assigned |
| Ownership complexity > 3 levels |
L2 |
Senior KYC Analyst |
EDD recommendation |
| Risk score = HIGH |
L3 |
EDD Analyst |
Branch to EDD_REVIEW |
| PEP flag + adverse indicators |
L3 |
EDD Analyst |
Deep due diligence |
| Sanctions POTENTIAL_MATCH |
L3 |
Sanctions Analyst |
Concurrent sanctions investigation |
| Sanctions CONFIRMED_MATCH |
L5 |
FCC + Legal |
Auto-freeze (PROHIBITED) |
| Policy exception required |
L4 |
FCC Reviewer |
Manual override |
| SLA breach (any task) |
L2 |
Supervisor |
Case escalated |
8. Error Handling & Edge Cases
| Scenario |
Behavior |
| Duplicate application |
Detected by matching registration number + jurisdiction. Returns 409 Conflict with existing application reference. |
| External registry unavailable |
Identity validation returns UNAVAILABLE. Task created for manual verification. Workflow continues on other parallel tracks. |
| Document exceeds size limit (10MB) |
413 Payload Too Large. Error message suggests compression or alternative submission. |
| Invalid state transition |
422 Unprocessable Entity. Message: "Cannot transition from X to Y. Allowed transitions: A, B, C." |
| Approve without rationale |
400 Bad Request. "Rationale is required for all onboarding decisions." |
| Approve own case (SoD violation) |
403 Forbidden. "Segregation of duties: case creator cannot approve the same case." |
| Customer already prohibited |
Cannot submit new application. 409 Conflict. "Customer is prohibited. Reason: Sanctions block dated 2024-03-15." |
| Ownership < 100% |
System flags gap. Analyst must resolve: either identify missing owners or document why ownership is incomplete. |
| UBO is a minor |
PEP-like flags raised. Requires additional guardian/representative documentation. |
| Document expired during onboarding |
Validation status changes to EXPIRED. New document requested. SLA timer pauses (WAITING_EXTERNAL). |
9. Integration Points
Inbound (who calls Onboarding)
| Caller |
Endpoint |
Purpose |
| Workflow Engine |
Internal (Temporal activity) |
Advance state, invoke document validation |
| Relationship Manager |
POST /applications |
Submit new application |
| Onboarding Specialist |
POST /{id}/classify |
Classify customer |
Outbound (who Onboarding calls)
| Target |
When |
Purpose |
| Name Screening |
After VALIDATION_PENDING |
Screen customer and all UBOs |
| Risk Rating |
After VALIDATION_PENDING |
Calculate risk score |
| Network Analysis |
After VALIDATION_PENDING (corporate only) |
Map ownership graph |
| Audit Service |
Every state transition, decision, document upload |
Immutable logging |
| Notification Engine |
SLA warnings, task assignments, status changes |
Analyst alerts |
| Configuration Engine |
At workflow start and module invocation |
Get active config version |
Spec validated against PRD v1.0 requirements FR-ON-01 through FR-ON-06. Re-evaluate if state machine or document rules change.